i have an application with three screens and i want to display and hide a small uiview when the user swipes from the bottom of the ipad screen towards up. I know that this cannot be down with normal swipe gestures. I was wondering if you could tell me how to handle these kinds of swipe gestures?
Add UIPanGestureRecognizer to your view.
-(void) panDetected:(UIGestureRecognizer *) gesture
{
BOOL fromBottom = NO;
CGPoint loc = [gesture locationInView:self.view];
if(gesture.state == UIGestureRecognizerStateBegan)
{
if(loc is somewhere in the bottom of view)
fromBottom = YES;
}
else if(gesture.state == UIGestureRecognizerStateChanged)
{
// You can up your view with finger movement here
}
else if(gesture.state == UIGestureRecognizerStateEnded)
{
}
}
Related
So I created this pan gesture recogniser to detect my touch on several UIBUttons. The idea is. I am looking for having the ability to slide my finger over all the buttons and trigger each and single one of them while I touch them. Right now I am able to slide over all the buttons and trigger the sound with this code. There is one problem that is occurring and that is when I replace the sound file with the NSLog statement, Every tiny little move I make with my finger within the same button keeps on repeating sound over and over again really fast. It reacts to the slightest movement.
How can I enable only hearing the sound one time after my finger touches the button and have the ability to play the same sound again when my finger touches the same button again. Pretty much the effect you get when you touch or slide your finger over a real piano.
Can anyone help me out with this?
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[self.view addGestureRecognizer:pan];
}
//Method to handle the pan:
-(void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
//create a CGpoint so you know where you are touching
CGPoint touchPoint = [gesture locationInView:self.view];
//just to show you where you are touching...
NSLog(#"%#", NSStringFromCGPoint(touchPoint));
//check your button frame's individually to see if you are touching inside it
if (CGRectContainsPoint(self.button1.frame, touchPoint))
{
NSLog(#"you're panning button1");
}
else if(CGRectContainsPoint(self.button2.frame, touchPoint))
{
NSLog(#"you're panning button2");
}
else if (CGRectContainsPoint(self.button3.frame, touchPoint))
{
NSLog(#"you're panning button3");
}
Keep an NSMutableArray for detecting which sounds have been played since the last touch down event (pseudo code follows, please replace with proper method names and signatures):
NSMutableArray *myPlayedSounds;
void touchDown:(UITouch *) touch
{
//Empty played sounds list as soon as a touch event is sensed
[myPlayedSounds removeAllObjects];
}
-(void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
//create a CGpoint so you know where you are touching
CGPoint touchPoint = [gesture locationInView:self.view];
//just to show you where you are touching...
NSLog(#"%#", NSStringFromCGPoint(touchPoint));
//check your button frame's individually to see if you are touching inside it
if (CGRectContainsPoint(self.button1.frame, touchPoint) && [myPlayedSounds containsObject:#"button1"] == NO)
{
NSLog(#"you're panning button1");
[myPlayedSounds addObject:#"button1"];
}
else if(CGRectContainsPoint(self.button2.frame, touchPoint) && [myPlayedSounds containsObject:#"button2"] == NO)
{
NSLog(#"you're panning button2");
[myPlayedSounds addObject:#"button2"];
}
else if (CGRectContainsPoint(self.button3.frame, touchPoint) && [myPlayedSounds containsObject:#"button3"] == NO)
{
NSLog(#"you're panning button3");
[myPlayedSounds addObject:#"button3"];
}
}
I am working with two subviews. Each will be unique and have it's own "action".
Subview 1 = User can drag around the view, rotate, and zoom it
Subview 2 = When user moves finger across their screen an image is added at each point their finger touches.
I have both of these completed by using UIPanGestureRecognizer. My question is, how can I separate these two actions? I want to be able to add one subview, do what is required, and then when I add the other subview, prevent the previous actions from occurring.
Here is what I have tried, this is done in my panGesture method:
for (UIView * subview in imageView.subviews)
{
if ([subview isKindOfClass:[UIImageView class]])
{
if (subview == _aImageView)
{
CGPoint translation = [panRecognizer translationInView:self.view];
CGPoint imageViewPosition = _aImageView.center;
imageViewPosition.x += translation.x;
imageViewPosition.y += translation.y;
_aImageView.center = imageViewPosition;
[panRecognizer setTranslation:CGPointZero inView:self.view];
}
else if (subview == _bImageView)
{
currentTouch = [panRecognizer locationInView:self.view];
CGFloat distance = [self distanceFromPoint:currentTouch ToPoint:prev_touchPoint];
accumulatedDistance += distance;
CGFloat fixedDistance = 60;
if ([self distanceFromPoint:currentTouch ToPoint:prev_touchPoint] > fixedDistance)
{
[self addbImage];
prev_touchPoint = currentTouch;
}
}
}
}
If you want different gesture recognition in two different views, put separate recognizers on each view.
Usually, you want to have your view controller own and manage gesture recognizers, e.g.
- (void)viewDidLoad {
self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
self.panGesture.delegate = self;
[self.viewX addGestureRecognizer:self.panGesture];
// repeat with other recognisers...
}
Note that setting your controller as delegate of the gestureRecognizer is important: this enables you to handle the following delegate method from the view controller (which was the main question):
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// handle your logic, which gestureRecognizer should proceed...
return NO;
}
The handler method is the same is this example, but you can set up your own handlers as you like:
- (void)handleGesture:(UIGestureRecognizer*)gestureRecognizer {
// handle gesture (usually sorted by state), e.g.
// if(gesture.state == UIGestureRecognizerStateEnded) { ... }
}
I have UIView in which I want to be able to drag and drop subviews, and this is working fine once.
- (void)pan: (UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
CGPoint startPos = [recognizer locationInView:self];
for (UIControl *sv in self.subviews){
if ([sv pointInside:startPos withEvent:nil]){
self.controlBeingDragged = sv;
}
}
}
if (((recognizer.state == UIGestureRecognizerStateChanged) ||
(recognizer.state == UIGestureRecognizerStateEnded)) &&
self.controlBeingDragged){
CGPoint translation = [recognizer translationInView:self];
self.controlBeingDragged.center = CGPointMake(self.controlBeingDragged.center.x + translation.x, self.controlBeingDragged.center.y + translation.y);
[recognizer setTranslation:CGPointZero inView:self];
if (recognizer.state == UIGestureRecognizerStateEnded){
self.controlBeingDragged = nil;
}
}
}
However, if I try to drag the same UIControl again, the containing view no longer knows where it is. I can drag it again by starting at the original position, so clearly there is something I am not doing to inform the containing view that its subview has moved. But what?
I'm surprised it's even working once. The pointInside:withEvent: message requires a point in the receiver's coordinate system, but you're sending a point that's in the receiver's superview's coordinate system. You can use convertPoint:toView: for each subview to convert the point to the subview's coordinate system before sending the point in the pointInside:withEvent: message.
CGPoint subviewPoint = [self convertPoint:startPos toView:sv];
if ([sv pointInside:subviewPoint withEvent:nil]) {
...
You might also want to check whether the -[UIView hitTest:withEvent:] message can replace the entire subview-searching loop.
I have a splitViewController. This is in Detail VC
-(void)viewDidLoad
{
self.masterIsVisible = YES;
//a botton in navigation bar to hide or show the master view.
[button addTarget:self action:#selector(showOrHideMasterView)
forControlEventsTouchUpInside]
//gesture control to swipe right or left to slide master view in and out.
[swiperight addTarget:self action:#selector(showMasterView)];
[swipLeft addTarget:self action:#selector(hideMasterView)];
}
-(void)showOrHideMasterView
{
if (self.masterIsVisible)
[self hidemasterView]; self.masterIsVisible = NO;
else
[self showMasterView]; self.masterIsVisible = YES;
}
-(void)hideMasterView
{
//hides master view by substracting masterview's width from its origin.x
}
-(void)showMasterView
{
//shows master View by adding masterview's width to its origin.x
}
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController: (UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
return NO;
}
Everything almost works as intended.
Problem: In one orientation && master is NOT visible.. then device changes orientation.. the master View instead of sliding off the screen pushed the detail view the other way. I know thats because the flag now is set as masterIsVisible = NO instead of YES. What can I do to change the flag to YES on device rotation. looks trivial but cant seem to figure out.
I tried registering for devicechnagenotification in UIDevice but that did not work. The BOOL is YES in any Orientation. The apple example uses this but looks like thats not the right approach here.
Ok, I finally figured out to set the flag correctly for orientation change. I added the following method
-(void)willAnimateRotationToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (toInterfaceOrientation == UIInterfaceOrientationPortrait)
self.masterIsVisible = NO;
else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
self.masterIsVisible = YES;
else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
self.masterIsVisible = YES;
else if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
self.masterIsVisible = NO;
}
Currently I'm workign on a drawing app for the iPad. I need to reposition and rotate the toolbar in the app when it is put into a different orientation while keeping the the drawing area in the same place.
I found a method here for doing this. It uses the NSNotificationCenter to monitor for rotation changes. This calls a custom didRotate: method that will rotate and reposition my toolbar based on the UIDeviceOrientation.
This part works fine. However, whenever the side switch on the iPad is engaged to lock the orientation, the toolbar repositions to the location is was at launch.
For example: If I start the application in landscape left and rotate it to portrait, the toolbar will reposition to the bottom of the screen. However as soon as I engage the slide switch, it moves to the side of the screen for the landscape left orientation.
The methods I'm using for this are all below.
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
- (void)didRotate:(NSNotification *)notification {
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
UIInterfaceOrientation interfaceOrientation;
bool orientationFound = YES;
if (deviceOrientation == UIDeviceOrientationPortrait) {
interfaceOrientation = UIInterfaceOrientationPortrait;
} else if (deviceOrientation == UIDeviceOrientationPortraitUpsideDown) {
interfaceOrientation = UIInterfaceOrientationPortraitUpsideDown;
} else if (deviceOrientation == UIDeviceOrientationLandscapeLeft) {
interfaceOrientation = UIInterfaceOrientationLandscapeRight;
} else if (deviceOrientation == UIDeviceOrientationLandscapeRight) {
interfaceOrientation = UIInterfaceOrientationLandscapeLeft;
} else {
orientationFound = NO;
}
if (orientationFound) {
[self.toolbar changeToOrientation:interfaceOrientation withDuration:.25];
[self.tutorialOverlay changeToOrientation:interfaceOrientation];
}
}
- (void)changeToOrientation:(UIInterfaceOrientation)orientation withDuration:(float)duration {
float angle;
CGPoint origin;
if (orientation == UIInterfaceOrientationPortrait) {
angle = portraitAngle;
origin = self.portraitOrigin;
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
angle = portraitUpsideDownAngle;
origin = self.portraitUpsideDownOrigin;
} else if (orientation == UIInterfaceOrientationLandscapeLeft) {
angle = landscapeLeftAngle;
origin = self.landscapeLeftOrigin;
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
angle = landscapeRightAngle;
origin = self.landscapeRightOrigin;
}
[UIView animateWithDuration:duration animations:^{
self.transform = CGAffineTransformMakeRotation(angle);
CGRect rect = self.frame;
rect.origin = origin;
self.frame = rect;
}];
}
I'd strongly recommend against using this notification-based approach. Note that the name of the notification is UIDeviceOrientationDidChangeNotification; the device orientation is not the same as the interface orientation, and it leads to lots of little issues like this. (The device orientation, for example, includes UIDeviceOrientationFaceDown, which is never associated with an interface orientation.)
I'd suggest letting your view controller automatically rotate itself; this will place the toolbar, etc, for you. You can then override - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration to put the drawing area back to the orientation you want to maintain. (You'd use similar code to your changeToOrientation method above, but for the drawing area instead, and you don't need to create your own animation block. Also, the angles would all be negated, because you're undoing the change the view controller made.)
I just answered a similar question here.
Basically just allow the interface to rotate, but rotate the view you don't want to rotate 'back' in willRotateToInterfaceOrientation:duration:.