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];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *aTouch in touches) {
if (aTouch.tapCount >= 2) {
// The view responds to the tap
}
}
}
I'm using the code above to detect double tap gesture; however, how can I set the code to happen only once?
In other words, when you tap once, the character jumps. When you tap twice in quick succession, the character will double jump. But how do you set the taps in a way that the character will not continuously double jump and go higher off the single-view without initially taping once?
A very simple approach of achieving this is by declaring a global bool variable and set its value once the double tap has been detected!
Something like this:
#interface MyViewController()
{
bool isTapped;
}
#end
#implementation MyViewController
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *aTouch in touches) {
if (aTouch.tapCount >= 2) {
if(!isTapped) {
// The view responds to the tap
isTapped = YES;
}
}
}
}
#end
Hope this helps
Not sure if this helps and also just take one flag and set it yes or no accordingly:-
UILongPressGestureRecognizer *tapRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
tapRecognizer.delegate = self;
tapRecognizer.minimumPressDuration = //Up to you;
[self.someView addGestureRecognizer:tapRecognizer];
I have a view to which I've added both a pan and long press UIGestureRecognizer. The pan is used to move the view around. What I'd like to do is notice also that the touch has stopped moving (while remaining active) and trigger the long press.
What I find is that the long press is never triggered after the pan has begun. I've tried setting a delegate and implementing:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
NSLog(#"simultaneous %#", gestureRecognizer.class);
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
NSLog(#"require fail %#", gestureRecognizer.class);
return [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer self]];
// also tried return YES;
// also tried return [gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer self]];
}
I've tried fooling with the pan gr's allowableMovement, also to no avail. I'm just about to give up and use a timer in the pan gr that get's invalidated and then reset on moves, but I was hoping that the SDK would do the state machine stuff for me.
In case anyone else needs it, here's the code that works for me. The goal is to have a view that is sensitive to both long press and pan, including a long press that isn't preceded by a pan and vice versa.
// setup
#property (strong,nonatomic) NSTimer *timer; // triggers the long press during pan
#property (strong,nonatomic) UIView *longPressView; // need this to track long press state
// view is the view we're interested in panning and long pressing
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGR:)];
[view addGestureRecognizer:panGR];
// this starts a long press when no pan has occurred
UILongPressGestureRecognizer *longGR = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressGR:)];
[view addGestureRecognizer:longGR];
When a pan begins or changes, start a timer. If the timer expires before the pan ends (touch releases), then we have a long press.
- (void)panGR:(UIPanGestureRecognizer *)gr {
if (gr.state == UIGestureRecognizerStateBegan) {
[self startTimer:gr.view];
} else if (gr.state == UIGestureRecognizerStateChanged) {
[self startTimer:gr.view];
// do whatever you want to do with pan state in this method
// in my case, I'm translating the view here
} else if (gr.state == UIGestureRecognizerStateEnded) {
if (self.longPressView) {
[self longPressEnded];
} else {
[self.timer invalidate];
}
}
}
We give the timer user info of the view. You might need to store other parts of the gesture state, like location, etc. Do it the same way, with the user info dictionary.
- (void)startTimer:(UIView *)view {
if (self.longPressView) return;
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.8 target:self
selector:#selector(longPressTimer:)
userInfo:#{ #"view": view} repeats:NO];
}
-(void)longPressTimer:(NSTimer *)timer {
self.longPressView = timer.userInfo[#"view"];
[self longPressBegan];
}
Since the timer method won't have an associated gr, factor out all of the logic that we'd normally put in the gr handler so it can be called by both the timer handler and the gr handler.
- (void)longPressGR:(UILongPressGestureRecognizer *)gr {
if (gr.state == UIGestureRecognizerStateBegan) {
self.longPressView = gr.view;
[self longPressBegan];
} else if (gr.state == UIGestureRecognizerStateEnded) {
[self longPressEnded];
}
}
- (void)longPressBegan {
NSLog(#"long press began");
}
- (void)longPressEnded {
self.longPressView = nil;
NSLog(#"long press ended");
}
First of we have to register both long press and pag gesture events like,
let longPress = UILongPressGestureRecognizer()
longPress.delegate = self
longPress.addTarget(self, action: #selector(sendMessageLongPress(_:)))
let panGesture = UIPanGestureRecognizer()
panGesture.delegate = self
panGesture.addTarget(self, action: #selector(sendMessagePanGesture(_:)))
self.imgRecord.addGestureRecognizer(longPress)
self.imgRecord.addGestureRecognizer(panGesture)
then we have to setup to capture multiple touch events via delegate method. For this we have to extend UIGestureRecognizerDelegate and then use,
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
then we can implement events as we needed.
(in my case I wanted to cancel recording audio if user swipes, then I had to consider about touch started point and touch ended point as needed.)
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];
}
Is there a way to disable the fullscreen button of the MPMoviePlayerController ?
Just did it:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieEventFullscreenHandler:)
name:MPMoviePlayerWillEnterFullscreenNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieEventFullscreenHandler:)
name:MPMoviePlayerDidEnterFullscreenNotification
object:nil];
self.moviePlayer.controlStyle = MPMovieControlStyleEmbedded;
}
- (void)movieEventFullscreenHandler:(NSNotification*)notification {
[self.moviePlayer setFullscreen:NO animated:NO];
[self.moviePlayer setControlStyle:MPMovieControlStyleEmbedded];
}
Depending on your needs, you can also simply disable all user interactions on the player view.
player.view.userInteractionEnabled = NO;
You can set controlStyle to Fullscreen. these controls are somewhat different, but it doesn't feature a Fullscreen button!
[_moviePlayerController setControlStyle:MPMovieControlStyleFullscreen];
You could hide the playback controls and add your own custom ones, this will prevent the default buttons being rendered at all
I.e with
[player setMovieControlMode:MPMovieControlModeNone];
Unfortunately none of above worked for me properly, so picking the above I implemented the following (and worked fine):
Hide the full screen button.
Add this code in the method where you initialise the movie player.
....
//Because we have to wait until controllers are shown
[self performSelector:#selector(hideFullscreenButton) withObject:self afterDelay:0.5];
...
Add the methods:
-(void) hideFullscreenButton{
//Hide full screen mode button
[self hideFullscreenSubview:movieClip.view.subviews];
}
-(void) hideFullscreenSubview:(NSArray*)arr{
for(UIView *v in arr){
if([v.subviews count]>0)
[self hideFullscreenSubview:v.subviews];
else
NSLog(#"%#",v);
if(v.frame.origin.x==975 ){
v.hidden=TRUE;
}
}
}
The problem relies that there is no tag to identify which view you have to hide. In my case I figure it out by the view coordinates.
Overwrite tap gestures for do not allowing fullscreen zoom.
movieClip.controlStyle = MPMovieControlStyleEmbedded;
//Disable tap for not allowing that video control set on a full screen mode.
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(doSingleTap)];
singleTap.numberOfTapsRequired = 1;
[movieClip.view addGestureRecognizer:singleTap];
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(doDoubleTap)];
doubleTap.numberOfTapsRequired = 2;
[movieClip.view addGestureRecognizer:doubleTap];
[singleTap requireGestureRecognizerToFail:doubleTap];
And add the selector methods:
-(void) doSingleTap{
//DO NOTHING!!!
}
-(void) doDoubleTap{
//DO NOTHING!!!
}
There's a cheat:
MPMoviePlayerController *mpc = (...some instance...)
UIView *fsbutton = [[mpc view] viewWithTag:512];
[fsbutton setHidden:YES];
The main catch is, you have to do it in viewDidAppear: or similar, because the MoviePlayer view sets itself up somewhere inside didMoveToWindow or didMoveToSuperview, which happen after viewWillAppear:. So you get a brief flash of the fullscreen button. Other obvious catches include: brittle vs. Apple changing that 512 tag value (although it works in 3.2 - 4.2); and of course Apple would rather you not do this.
The endorsed solution is to set the control style to MPMovieControlStyleNone and roll your own transport controls, which is more work.
No, there is no way. Hopefully with the next update.
in order to disable switch to full screen mode, either form button or pinch gesture, you can use this:
moviePlayer.controlStyle = MPMovieControlStyleNone;
moviePlayer.view.userInteractionEnabled =NO;
Wired does this. For the videos that start in fullscreen, they have the standard MPMoviePlayerController controls, but are missing the fullscreen buttons. And they're using the standard built-in ones, since they suddenly got an AirPlay button with 4.2.
Simple block to remove pinch zoom here
Hope it help
it work with me on iOS6
for (UIView *view in moviePlayer.view.subviews) {
for(UIPinchGestureRecognizer *pinch in view.gestureRecognizers){
if([pinch isKindOfClass:[UIPinchGestureRecognizer class]])
[view removeGestureRecognizer:pinch];
}
}
This worked on iOS 7, iPhone 5s.
Add Notification:
MPMoviePlayerDidEnterFullscreenNotification : #"moviePlayFullscreenNote:"
- (void)moviePlayFullscreenNote:(NSNotification*)notification
{
if (notification.object == self.videoPlayer)
{
[self.videoPlayer setFullscreen:NO animated:YES];
self.videoPlayer.controlStyle = MPMovieControlStyleEmbedded;
}
}
Notice that I only listen for "DID" and not the "WILL" notification as well as running it animated. I think this works as it gives the system time to react. When I used the "WILL" and "DID" as noted in answers above it led to a black screen with no controls. There is a slight glitch that is visible when the transition occurs, but I need the play/scrub buttons from embedded.
Fullscreen button along with pause button can be removed.
[self.videoPlayer setControlStyle:MPMovieControlStyleNone];
If the only thing you want to do is disable pinch to go full screen (i.e. keep interaction enabled and whatever control style you want), you can use this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *set = [event allTouches];
NSArray *arr = [set allObjects];
for (int i = 0; i < arr.count; i++) {
UITouch *touch = (UITouch *) [arr objectAtIndex:i];
NSArray *recognisers = touch.gestureRecognizers;
for (UIGestureRecognizer *recogniser in recognisers) {
if (recogniser.enabled && [recogniser isMemberOfClass:[UIPinchGestureRecognizer class]]) {
recogniser.enabled = NO;
}
}
}
}
This is the Swift version of the first solution of Javier Calatrava LlaverÃa:
func hideFullScreenButton() {
self.hideFullScreenSubview((self.moviePlayerController?.view.subviews)!)
}
func hideFullScreenSubview(subviews: [UIView]) {
for view: UIView in subviews {
if view.subviews.count > 0 {
self.hideFullScreenSubview(view.subviews)
}
if view.frame.origin.x == 631 {
view.hidden = true
}
}
}
And when the user taps on Play:
self.performSelector(#selector(VideoViewController.hideFullScreenButton), withObject: self, afterDelay: 0.5)
(VideoViewController is the view controller in which I have the MPMoviePlayerController)
I know, it's a little outdated, but anyway. I did some research in that direction, and looks like a found an answer. I do not know, why it's working, but it is.
-(void) playMovieAtURL: (NSURL*) theURL {
MPMoviePlayerController* theMovie =
[[MPMoviePlayerController alloc] initWithContentURL: theURL];
//That line is for ARC. Without it, it may not work.
self.moviePlayer = theMovie;
theMovie.scalingMode = MPMovieScalingModeAspectFill;
theMovie.controlStyle = MPMovieControlStyleFullscreen;
theMovie.repeatMode = MPMovieRepeatModeOne;
//Here you'd better use your custom ViewController subclass, if you want autorotating and all that stuff.
UIViewController * vc = [UIViewController new];
[vc.view addSubview:theMovie.view];
theMovie.fullscreen = YES;
theMovie.view.frame = vc.view.bounds;
vc.view = theMovie.view;
[self presentModalViewController:vc animated:YES];
theMovie.fullscreen = YES;
[theMovie prepareToPlay];
[theMovie play];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMovieFinishedCallback:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
}
// When the movie is done, release the controller.
-(void) myMovieFinishedCallback: (NSNotification*) aNotification
{
[self dismissModalViewControllerAnimated:YES];
MPMoviePlayerController* theMovie = [aNotification object];
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: MPMoviePlayerPlaybackDidFinishNotification
object: theMovie];
[self.moviePlayer.view removeFromSuperview];
self.moviePlayer = nil;
// Release the movie instance created in playMovieAtURL:
}
Put a UIView or UIButton with transparent background on top of the view that shows the video, so that the user won't be able to tap on the view that contains the video.