I have a custom vertical range slider and handling the movements of heads using touchBegan, touchMoved and touchEnded. When I try to slide the head, it slides a bit and after that touch is cancelled and interactive dismiss transition starts on iOS 13. I want to prevent the touch to transfer while sliding, to the superview. How we can achieve that.
Thanks in advance.
Try using another gesture recognizer to the view with another selector
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tappedOnBubble)];
[self.bubbleView addGestureRecognizer:tap];
UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tappedOnMainView)];
[self.view addGestureRecognizer:tap2];
-(void)tappedOnMainView
{
NSLog(#"touched on main View");
[self.vwToShow setHidden:NO];
}
-(void)tappedOnView
{
NSLog(#"tapped on slider");
[self.vwToShow setHidden:YES];
}
UIView inherits from UIResponder, and basic touch events are detected by the view that triggers the touches began events. The subviews that you added in the main view also responds to the touches began method.Thats very basic. You have also added a selector method with tap gesture recognizer.
If you still want to use touchBegan , I think you should do something like this:
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event
{
if(theTouchLocation is inside your bubble)
{
do something with the touch
}
else
{
//send the touch to the next view in the hierarchy ( your large view )
[super touchesBegan:touches withEvent:event];
[[self nextResponder] touchesBegan:touches withEvent:event];
}
}
Related
I am making a custom iOS keyboard and have a UIControl subclass to represent my button. I am trying to get the same behaviour as the normal iOS keyboard:
User begins touch on one button
User drags over other buttons (need to detect this so they can highlight/dehighlight accordingly)
Register the actual keyboard "press" when the user lifts their finger; that is, the touch ends
I am testing using the touch tracking methods like this:
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super beginTrackingWithTouch:touch withEvent:event];
NSLog(#"Begin for %#", [self label]);
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super continueTrackingWithTouch:touch withEvent:event];
NSLog(#"Continue for %#", [self label]);
return YES;
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super endTrackingWithTouch:touch withEvent:event];
NSLog(#"End for %#", [self label]);
}
These methods are all called, except they are only ever called on the UIControl where the touch began.
What is the best way to recognise touches coming and going across all my buttons? Do I have to do it all via the parent view?
I'll select a better answer if offered... but in case anybody finds this by search, I've managed to get what I need this way:
Set userInteractionEnabled to NO for the button class (UIControl subclass)
Don't override any touch methods in the button class
Implement touchesBegan:withEvent:, touchesMoved:withEvent: and touchesEnded:withEvent: in the view controller
On each event, extract the location from the UITouch object
Iterate over all of the button subviews and find the one containing the touch location:
- (MyButton *)buttonForTouch:(UITouch *)touch
{
CGPoint windowLocation = [touch locationInView:keyboardView];
for (MyButton *button in buttons) {
if (CGRectContainsPoint([button frame], windowLocation)) {
return button;
}
}
return nil;
}
Having determined which button the user is interacting with, make the view controller send messages to the relevant buttons to adjust their appearance
If appropriate, keep a reference to the UITouch instance in touchesBegan:withEvent: so you can be sure that you're tracking the same one in the other methods
I think that you should have a single big UIControl which has different subviews (like UIButton) but tracks touches by itself like you did already but finds out which subview to highlight depending on the touch position.
I'm working on a Tap game, tap the screen to start the game.
I have this code and I need to make this animation to start after tap the screen. Now Is running when the game are loading (without tap the screen). What I need to change it to start this animation after the user tab (touch) on the screen? Thanks for your help.
[super viewDidLoad];
// Set Delay on Animations when Game Start - Animations Area ***** PERFORMS ****** 0.1
[self performSelector:#selector(Animation) withObject:nil afterDelay:0.1];
Add a tap gesture recognizer to your view and start the animation in the selector.
-(void) viewDidLoad
{
[super viewDidLoad];
UITapGestureRecognizer *tapGestureRg = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(beginAnimation)];
[self.view addGestureRecognizer:tapGestureRg];
}
-(void) beginAnimation
{
// Set Delay on Animations when Game Start - Animations Area ***** PERFORMS ****** 0.1
[self performSelector:#selector(Animation) withObject:nil afterDelay:0.1];
}
If you want to begin animation on touch begins then override -(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event and start the animation there.
You can add the Tap Gesture to screen.If you have UIViewController and you want to start animation on tap of UIViewController view tap then you can use this code as it is or you can change the view on which you want tap and start animation
-(void) viewDidLoad
{
[super viewDidLoad];
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSingleTap:)];
[self.view addGestureRecognizer:singleFingerTap]; //Replace the self.view if you want to add single tap on another view with your view refrence
}
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
[self Animation]; //You should rename this method as animation.Follow naming conventions.
}
I am following through the Stanford iOS class and have a bit of a design question. In the class we are making a card matching game that has some 20 cards on screen or so. I recently made the cards UIViews so I could draw them properly.
I gave them each a method tap that will swap faceUp to YES/NO, thus flipping the cards. I add the gesture recognizer to each in my ViewController and it works. The individual cards know when they're touched and flip.
However, I need to know in my ViewController that a cardView has been touched... and which one.
How/what ways do I have to do this? Can I broadcast something in my View that the ViewController will listen for? Can I have my viewController handle that taps (but is there a way to get the sending view if I do this?) I apologize if this is really base, but I'm new to iOS and would like to not learn by patching and implementing a broken MVC pattern.
EDIT: Just for information, this is my final implementation.
Each CardView has a tap recognizer on it. When a tap is recognized it calls:
- (void)cardTapped:(UIGestureRecognizer *)gesture
{
UIView *view = [gesture view]; // This method is what I was looking for.
if ([view isKindOfClass:[PlayingCardView class]]) {
PlayingCardView *playingCardView = (PlayingCardView *)view;
[playingCardView flip]; // flips card
// Game code
if (!self.game.hasStarted) {
[self startGame];
}
int cardIndex = [self.cardViews indexOfObject:playingCardView];
[self.game chooseCardAtIndex:cardIndex];
[self updateUI];
}
}
The tag property will tell you which view has been tapped. Set the proper tag when you create your view and in your action method that's been triggered on tap you can call a delegate method that will notify your delegate about which view has been tapped. Make your viewcontroller has the delegate and it will received the notification.
// your target method will look like this:
- (void) didTap:(id)sender {
//... your code that handle flipping the card
[self.delegate didTapOnCard:self]; // where delegate is your view controller
}
You can use touchesBegan method to detect which view was tapped.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
NSLog(#"%d", [touch view].tag); // Considering you have set tags for your UIViews..
if([touch view] == cardView1) // Considering you have a view as cardView1
{
NSLog(#"cardView1 is tapped");
}
}
In your UIView card class add
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)e {
UITouch *touch = [touches anyObject];
if ([self pointInside:[touch locationInView:self] withEvent:nil]) {
[self touchesCancelled:touches withEvent:e];
// Add your card flipping code here
}
}
The broadcast approach: in your UIView's tap method, send a notification:
[[NSNotificationCenter defaultCenter] postNotificationName:#"cardTapped" object:self userInfo:#{ #"card": #(self.cardId), #"faceUp": #(self.faceup) }];
and in your ViewController subscribe to that notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(cardTapped:) name:#"cardTapped" object:nil];
and implement
-(void)cardTapped:(NSNotification*)notification
{
int card = [[notification.userInfo objectForKey:#"card"] intValue];
BOOL faceUp = [[notification.userInfo objectForKey:#"faceUp"] boolValue];
}
I've tried subclassing UICollectionView and overriding touchesBegan:withEvent: and hitTest:WithEvent:, and both of those methods trigger when I touch a cell. However, if I touch the space between the cells, nothing happens at all. Here's what I've created:
#interface WSImageGalleryCollectionView : UICollectionView
#end
..and..
#implementation WSImageGalleryCollectionView
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touches began");
[super touchesBegan:touches withEvent:event];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(#"Hit test reached");
return [super hitTest:point withEvent:event];
}
#end
Note: gesture recognizers seem to have the exact same issue, which is why I tried going lower-level with touchesBegan.
You just need to set a view as background view that you can then add a gesture recognizer to like:
collectionView.backgroundView = [[UIView alloc] init];
[collectionView.backgroundView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapOnBackgroundRecognized)]];
You must have got something else that is preventing touchesBegan from firing.
Subclassing UICollectionView and using that subclass inside of a UICollectionViewController will allow you to detect in-between cells taps in touchesBegan.
I have just tried it out by modifying Apple's CollectionView-simple sample.
In my case, the problem was that you can click on the background whenever it is "clear" colored. Otherwise simply setting a background color makes the background clickable. I don't quite know why this is, but the simple fix was to just give it a background color.
Edit: Actually, this may have had something to do with setting the "opaque" flag.
I have a UIControl inside a UIScrollView. In my UIControl's init, I rig up some touch event handlers, e.g.
[self addTarget:_delegate
action:#selector(touchedDown) forControlEvents:UIControlEventTouchDown];
iOS6 and iOS7 behave differently when I do the following:
Swipe the UIScrollView to start scrolling
Tap the UIScrollView to stop scrolling
In iOS6, my app continues to behave as intended: the tap at step #2 does not call touchedDown -- the UIScrollView swallows the touch event as it immediately stops scrolling.
But in iOS7, the UIScrollView stops scrolling as expected, while touchedDown is still called.
Was there a documented API change? I'd like my app to behave identically to iOS6 in iOS7.
workaround for iOS 7
#interface UIScrollViewFixed : UIScrollView
#end
#implementation UIScrollViewFixed
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.isDragging || self.isDecelerating) {
return self;
}
return [super hitTest:point withEvent:event];
}
#end
Just replace the event type
UIControlEventTouchDown must be UIControlEventTouchUpInside
Not very elegant, but in the absence of any better ideas, here's what's working for me now:
On the UIScrollView, set canCancelContentTouches to YES and delaysContentTouches to NO.
In the UIScrollViewDelegate, toggle the UIScrollView's subview's userInteractionEnabled property when the UIScrollView scrolls:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[_contentView setUserInteractionEnabled:NO];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate) {
[_contentView setUserInteractionEnabled:YES];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[_contentView setUserInteractionEnabled:YES];
}
Subclass the UIScrollView and implement:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
return YES;
}
Subclass the UIControl and implement touchesCancelled:withEvent to reverse whatever the UIControlEventTouchDown handler does:
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
//custom logic
}
Same here with UIButtons on a UIScrollView. This is my solution for now.
Instead of using the contents UIControlEventTouchDown event:
[button addTarget:_delegate
action:#selector(touchedDown) forControlEvents:UIControlEventTouchDown];
I implemented the UIResponder touchesEnded method in my content UIViewController:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
// my scroll content touch logic
}
If the user touches the content and starts dragging the touchesEnded handler will not be called. The UIResponder touchesCanceled method will.
If the user does not drag the UIscrollview the touchesEnded handler is fired, which can be used for touch logic.