How would I go about adding gesture events to uipickerview to change tabs? I have to create a custom class, however, I don't know how to handle the uipickerview. I current have gestures present in uiviews to do this, but I'm having trouble with the uipickerview.
My code for the views:
#define HORIZ_SWIPE_DRAG_MIN 100
CGPoint mystartTouchPosition;
BOOL isProcessingListMove;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint newTouchPosition = [touch locationInView:self.view];
if(mystartTouchPosition.x != newTouchPosition.x || mystartTouchPosition.y != newTouchPosition.y) {
isProcessingListMove = NO;
}
mystartTouchPosition = [touch locationInView:self.view];
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = touches.anyObject;
CGPoint currentTouchPosition = [touch locationInView:self.view];
// If the swipe tracks correctly.
double diffx = mystartTouchPosition.x - currentTouchPosition.x + 0.1; // adding 0.1 to avoid division by zero
double diffy = mystartTouchPosition.y - currentTouchPosition.y + 0.1; // adding 0.1 to avoid division by zero
if(abs(diffx / diffy) > 2.5 && abs(diffx) > HORIZ_SWIPE_DRAG_MIN)
{
// It appears to be a swipe.
if(isProcessingListMove) {
// ignore move, we're currently processing the swipe
return;
}
if (mystartTouchPosition.x < currentTouchPosition.x) {
isProcessingListMove = YES;
self.tabBarController.selectedViewController = [self.tabBarController.viewControllers objectAtIndex:0];
return;
}
else {
isProcessingListMove = YES;
self.tabBarController.selectedViewController = [self.tabBarController.viewControllers objectAtIndex:2];
return;
}
}
else if(abs(diffy / diffx) > 1)
{
isProcessingListMove = YES;
[super touchesMoved:touches withEvent:event];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
isProcessingListMove = NO;
[super touchesEnded:touches withEvent:event];
}
Thanks for the help.
You CAN subclass UIPickerView. The problem is that it's comprised of nine subviews, one of which, called UIPickerTable, is receiving the touch events like touchesBegan:withEvent: to the rows you want. I was able to successfully intercept these with the code included at the end of this post:
Responding to touchesBegan in UIPickerView instead of UIView
The other answers/comments there are helpful too. I wanted to clear the air, not for someone doing something non-standard (as in this question), but for someone who arrived here wishing to subclass UIPickerView, because the first line of the accepted answer is dead wrong.
You can't subclass UIPickerView.
However, since a picker view must be displayed in another view (since it doesn't take up the entire screen) you can trap the touches in that view's controller and filter for gestures.
(Assuming I understand what your trying to do...) I would warn that swiping a picker view to change tabs is a non-standard UI and will probably confuse your users. Since a picker view is perceived as a type of control, they will expect only the normal spinning action of the picker. How would they even know to swipe a picker view horizontally?
I left it alone. It would have been confusing, you're right.
ZT> You can't subclass UIPickerView.
"In order to make the picker view spin longer, I subclassed UIPickerView. My subclass had exactly one method" from Longer Spinning and Blurring. Also see UIPickerView Class Reference: "The UIDatePicker class uses a custom subclass of UIPickerView to display dates and times."
Related
I have an image view. i detected touch in image view like this
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
int viewTag=[touch view].tag;
if ([[touch view] isKindOfClass:[UIImageView class]])
{
//My code
}
}
and touches moved on image view. whenever my touch moved out off the image view in that particular time i need one alert view. how to detect that touch over from the image view in touches moving?...
I recommend using a UIPanGestureRecognizer and adding it to a larger super-view of the image-view you want to detect on. That way even if the touch starts outside and moves into and out of your image-view you can follow the movement of the touch in your gesture handler.
It's pretty easy, make a method called handlePan: for example, create the gesture recognizer using your handler method, add it to the appropriate super-view. Now whenever the gesture is active and the touch moves your handler method will get called and you can check to see if it is inside your image view.
You should use this method...
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
int viewTag=[touch view].tag;
if ([[touch view] isKindOfClass:[UIImageView class]])
{
//My code
}
else
{
//show the alertView here
}
}
and to check that the initial click was on the imageView you have to set a flag in the touchesBegan method... and check it accordingly in the touchesMoved method
You may add a transparent UIButton of the same size on top of the UIImageViewand track UIControlEventTouchDragOutside
[button addTarget:self action:#selector(draggedOutside:) forControlEvents:UIControlEventTouchDragExit];
Imagine your keyboard. Imagine yourself placing one finger down one key, then (while holding down) moving your finger all the way across to another key on the keyboard. Now, imagine that each key on the keyboard is a UIButton. when you are holding your finger on the current key, this key (UIButton) is highlighted. Then, when the user moves across to another key, the first key is no longer highlighted, and the current key that is pressed down, is highlighted.
Now, I have a 6 x 8 grid of UIButtons each 53 x 53 pixels. So, I have 48 UIButtons. I want to replicate this kind of idea. The button that the users finger is upon, will have a image that is slightly lighter (to look selected like), and all others will will not be slightly lighter.
Here is my idea of how to go about this,
1) Create all 48 UIButtons in viewDidLoad. Add the lighter image to UIControlStateHighlighted for all UIButtons.
2) Add some sort of target for touchUpOutside that somehow makes the current button not highlighted and not usable. (maybe set the highlighted and userInteractionEnabled to no). But then how can I tell the next button to be used? And how can I say that I want the specific UIButton that the users fingers are under to become highlighted and in use to detect gestures and stuff.
Also, this touchUpOutside method may not work because all the buttons are right next to each other, and I think you have to drag far out to run this touchUpOutside.
It's also important to note that the buttons are not placed in equal rows and columns. Sometimes one button will actually be a UIImage but look like a UIButton. So, doing some sort of fast enumeration comparing frames for some reason will not work.
Two observations:
I generally use gesture recognizers for stuff like this, not the various touchUp... methods.
I know you said you don't want a fast enumeration through the subviews, but why not? Just because some are different classes than others is not a problem (because you could just test the isKindOfClass method). (And, as an aside, I'm not sure why some would be buttons and some would not.) Just because they don't line up really makes no difference, either.
Anyway, for stuff like this, I frequently will put the gesture recognizer on the shared parent view (e.g. typically the view controller's view), and then enumerate through the subviews to see in which the current CGPoint is contained?
CGPoint location = [sender locationInView:self.view];
for (UIView *subview in self.view.subviews)
{
if (CGRectContainsPoint(subview.frame, location))
{
// do your appropriate highlighting
if ([subview isKindOfClass:[UIButton class]])
{
// something for buttons
}
else if ([subview isKindOfClass:[UIImageView class]])
{
// something for imageviews
}
return;
}
}
I don't know if any of this makes sense to you, but it seems easiest to me. If you clarify your intent, perhaps I can refine my answer further.
Just as a practical example, you could define a continuous gesture recognizer that kept track of the first and last subview that was clicked on (and if you don't start your gesture on a subview, no gesture is generated, and if you don't stop your gesture on a subview, it cancels the whole thing), e.g.:
#interface SubviewGestureRecognizer : UIGestureRecognizer
#property (nonatomic,strong) UIView *firstSubview;
#property (nonatomic,strong) UIView *currentSubview;
#end
#implementation SubviewGestureRecognizer
- (id) initWithTarget:(id)target action:(SEL)action
{
self = [super initWithTarget:target action:action];
if (self)
{
self.firstSubview = nil;
self.currentSubview = nil;
}
return self;
}
// you might want to tweak this `identifySubview` to only look
// for buttons and imageviews, or items with nonzero tag properties,
// or recursively navigate if it encounters container UIViews
// or whatever suits your app
- (UIView *)identifySubview:(NSSet *)touches
{
CGPoint location = [[touches anyObject] locationInView:self.view];
for (UIView *subview in self.view.subviews)
{
if (CGRectContainsPoint(subview.frame, location))
{
return subview;
}
}
return nil;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
if ([touches count] != 1)
{
self.state = UIGestureRecognizerStateFailed;
return;
}
self.firstSubview = [self identifySubview:touches];
self.currentSubview = self.firstSubview;
if (self.firstSubview == nil)
self.state = UIGestureRecognizerStateFailed;
else
self.state = UIGestureRecognizerStateBegan;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if (self.state == UIGestureRecognizerStateFailed) return;
self.currentSubview = [self identifySubview:touches];
self.state = UIGestureRecognizerStateChanged;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
self.currentSubview = [self identifySubview:touches];
if (self.currentSubview != nil)
self.state = UIGestureRecognizerStateEnded;
else
self.state = UIGestureRecognizerStateFailed;
}
- (void)reset
{
[super reset];
self.firstSubview = nil;
self.currentSubview = nil;
}
You could then set this up in viewDidLoad:
SubviewGestureRecognizer *recognizer = [[SubviewGestureRecognizer alloc] initWithTarget:self action:#selector(handleTouches:)];
[self.view addGestureRecognizer:recognizer];
And then define your handler as such (this works with buttons and image views, taking advantage of the fact that both support setHighlighted):
- (void)handleTouches:(SubviewGestureRecognizer *)sender
{
static UIControl *previousControl = nil;
if (sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateChanged)
{
if (sender.state == UIGestureRecognizerStateBegan)
previousControl = nil;
UIView *subview = sender.currentSubview;
if (previousControl != subview)
{
// reset the old one (if any)
[previousControl setHighlighted:NO];
// highlight the new one
previousControl = (UIControl *)subview;
[previousControl setHighlighted:YES];
}
}
else if (sender.state == UIGestureRecognizerStateEnded)
{
if (previousControl)
{
[previousControl setHighlighted:NO];
NSLog(#"successfully touchdown on %# and touchup on %#", sender.firstSubview, sender.currentSubview);
}
}
else if (sender.state == UIGestureRecognizerStateCancelled || sender.state == UIGestureRecognizerStateFailed)
{
[previousControl setHighlighted:NO];
NSLog(#"cancelled/failed gesture");
}
}
Here is a fairly simple way to do this. Assuming you are creating your buttons programmatically (it would be a huge pain to do this with interface builder). This method works in subclasses of UIViewController, UIView, and UIGestureRecognizer.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//get the touch object
UITouch *myTouch = [touches anyObject];
//get the location of the touch
CGPoint *myPoint = [myTouch locationInView:self.view];
//detect if the touch is in one of your buttons
if ( CGRectContainsPoint(myButton.frame, myPoint){
/*do whatever needs to be done when the button is pressed
repeat the CGRectContainsPoint for all buttons,
perhaps using a while or for loop to look through an array?*/
}
}
This method only detects when the user puts their finger down, you will need to use these methods to detect other movements:
touchesMoved:withEvent detects when the finger moves across the screen without lifting up
touchesEnded:withEvent detects when the finger is lifted off the screen
touchesCancelled:withEvent: detects when something interrupts the app, like receiving a call or locking the screen
How can I make it so that while the user is playing on the joystick to move the character at the centre they can also touch the bottom right of the screen (the second touch) to fire the gun? I have looked at other questions but I still cant figure it out...
this is basically the code....:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//make the touch point...
UITouch *touch = [[event allTouches]anyObject];
CGPoint point = [touch locationInView:touch.view];
if (//touching a certain area (on the joystick)) {
//do stuff
}
else if (point.x > 300 && point.y > 200) {
/fire method
}
}
so basically how do I call touchesBegan again to find the position of CGPoint point, and work out if the fire method should be called?
Thanks
EDIT:
I have tried to do that 2nd option and done this:
in view controller.h I added:
#interface fireView: UIView
#end
and in .m I added:
#implementation fireView -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"hi");
}
#end
....but it doesn't log/print "hi"?
Use 2 UIGestureRecognizers. You can create 2 invisible views of needed size - one for joystick and one for fire button. For every view use single gesture recognizer. And then you will be able to handle taps on these view by in different methods without checking if it was fire or joystick.
Let's think you have 2 views - joystickView and fireView already. Then do it like
UITapGestureRecognizer* fireTapGestureRec= [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(fireTapped:)];
fireTapGestureRec.delegate = self;
fireTapGestureRec.numberOfTapsRequired = 1;
fireTapGestureRec.numberOfTouchesRequired = 1;
[fireView addGestureRecognizer:fireTapGestureRec];
[fireTapGestureRec release];
and write fireTapped: to handle the event. And the same thing for joystick.
EDIT
Second option (suggested in comments)
Make subclasses of UIView, like
#interface fireView: UIView
#interface joystickView: UIView
and for each subclass write it own touchesBegan: or touchesEnded:
I have a UIView with multiple text boxes. Now i have used delegates to change the responder from from text field to another. In this case my key board goes away when the user comes to last text field.
but now i want to hide my key board when the user touches UIView(touches any where on screen apart from text boxes). Can some one help me with this.
Thanks
Use resignFirstResponder in touchesBegan, like so:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
if(touch.phase == UITouchPhaseBegan) {
[self.myTextField resignFirstResponder];
}
}
You may need to call this on multiple text fields if you're not sure where the focus is currently located; I haven't tested that case.
Additionally, in Interface Builder, you'll need to enable the "User Interaction Enabled" checkbox for the view, or programatically use:
myView.userInteractionEnabled = YES;
Just call this in your view controller when you want to hide the keyboard.
[self.view endEditing:NO];
I use
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
if(touch.phase==UITouchPhaseBegan){
//find first response view
for (UIView *view in [self.view subviews]) {
if ([view isFirstResponder]) {
[view resignFirstResponder];
break;
}
}
}
}
According to Beginning iPhone Development, you draw a round rect button so that it covers your entire UI; the complete screen. Then go to the Layout menu and click Send to Back. Then in the inspector, change the button's type from round rect to Custom. Now add a touch up inside event to this button and attach it to a method which handles it. Within the body of this method, make sure you have the statements:
[myTextFieldOne resignFirstResponder];
[myTextFieldTwo resignFirstResponder];
Basically send the resignFirstResponder message to each of your text fields, or any field that can produce a keyboard.
I'm actually really new to the iPhone SDK. I don't know if this is the best method, but it works and it's what I learned from the aforementioned book. Hope it helps.
A super easy way to do this is working on what PCheese said.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
if(touch.phase == UITouchPhaseBegan) {
[self.view endEditing:YES];
}
}
I previously used [self.view endEditing:YES]; for a button event, but when combined with the previous answer it works a treat. Much easier, and you don't have to do anything else - just put it in your .m file. Also, this works with all (at least, as far as I have seen) text fields (works for UITextField and UITextView).
I know OP probably won't still need this but I just want to share it for others with the same problem.
Thanks Blaenk, I was trying to work out how to do this and didn't realise I could put a button in the background, nice trick! Here's my contribution (new to this site and to Cocoa Touch so it may not be the most robust code ever, but it's working so far...):
I call this method from the touchUpInside event on my UIButton:
-(void)closeKeyboard:(id)sender {
UIView *theFirstResponder = [self findFirstResponder];
if (theFirstResponder) {
[theFirstResponder resignFirstResponder];
}
}
Where this loop finds the firstResponder:
- (UIView *)findFirstResponder {
UIView *firstResponderView = nil;
for (UIView *view in [self entryFields]) {
if ([view isFirstResponder]) {
firstResponderView = view;
break;
}
}
return firstResponderView;
}
It is dependent on each of the UITextField controls in the view having a tag assigned to them (again, can do that in Interface Builder).
My two cents anyway, though i'd better give something back!
SO I agonized over this and I figured it out..
you don't need the other button or anything.
All you need to do is select "Files owner", in the inspector drag from its textField outlet (or whatever you named it) to your actual textfield (via whatever you named it) This will be in addition to whatever inputs you already had wired up.
and ofcourse in addition to the Control outlet (UIview changed to UIControl via inspector) to files owner via backgroundtouched...the first thing we all tried
it work for me
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view endEditing:NO];
}
only need into self.view have an UItextField
http://objdev.com/2013/11/Dismissing-iOS-Keyboard-Self-View-EndEditing
The most appropriate way of solving this problem is using the following method:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if(![touch.view isMemberOfClass:[UITextField class]]) {
[touch.view endEditing:YES];
}
}
Note: This does not work on UIScrollView instances.
Change the class UIView to class UIControl from the identify tab of inspector.
Add this:
- (IBAction)tabBackground:(id) sender;
to your .h file.
Add this to your .m file:
- (IBAction)tabBackgroup:(id) sender {
[nameField resignFirstRespnder];
[numberField resignFirstResponder];
}
Connect your tabBackground from the inspector, action received section to the UIView (which is an an UIControl) and you're good to go.
For swift 4.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if touch?.phase == UITouchPhase.began {
touch?.view?.endEditing(true)
}
}
I have a view derived from a UIScrollView controller that I use to page through images in a library. That works fine.
I overlayed a UIView on the right side to handle touches for scrolling quickly through the list of images. My problem is that if I pass the touches through to either 'super' or 'nextResponder' they never make it to the UIScrollView object below.
My question is how can I force the UIScrollView below to handle the touches that I don't need to handle? I'm setting a timer for 0.3 seconds that during that time all touches are passed to the UIScrollView to handle. So if the user started a swipe gesture to turn the page, it will happen.
Here's the code for the touchesBegan method:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
// Start a timer to trigger the display of the slider and the processing of events.
touchesTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:#selector(showSlider:) userInfo:nil repeats:NO];
// Where are we at right now?
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
lastPage = [self page:currentPoint.y];
// Pass the event though until we need it.
// [self.nextResponder touchesBegan:touches withEvent:event];
if ([touch.view isKindOfClass:[UIScrollView class]])
{
if (self.nextResponder != nil &&
[self.nextResponder respondsToSelector:#selector(touchesEnded:withEvent:)])
{
[self.nextResponder touchesEnded:touches withEvent:event];
}
}
}
I would try using a regular UIViewController rather than a UIScrollViewController and then add the scroll view object where you need it and put the scroll faster view you want on the right side. This way they are separate and the touches won't get confused.
Here's an easier solutions that worked well for me:
In the UIView on top of the UIScrollview make the width and height both 0 (so that the view is no longer technically on top of the UIScrollView) and set clipsToBounds = NO (so that the contents of the view still show up on top of the UIScrollView).
self.OverlayView.clipsToBounds = NO;
CGRect frame = self.OverlayView.frame;
self.OverlayView.frame = CGRectMake(frame.origin.x, frame.origin.y, 0, 0);
Note that if the UIView contains interactive controls then they will no longer work. You'll need to move it into it's own view above the UIScrollView.