I have a scroll view which shows a image view. I am trying to handle UIRotationGestureRecognizer on the image view. I get the event for rotation and apply the required transform on the same. The image gets properly rotated in the scroll view. Then when I do any operations in the scroll view like zoom or pan the image rotation and position goes for a toss
_mainView is the subView of UIScrollView which is also used for zooming
UIRotationGestureRecognizer *rotationGesture=[[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotationGesture:)];
[_mainView addGestureRecognizer:rotationGesture];
[rotationGesture release];
-(void) rotationGesture:(UIRotationGestureRecognizer *) sender {
if(sender.state == UIGestureRecognizerStateBegan ||
sender.state == UIGestureRecognizerStateChanged)
{
sender.view.transform = CGAffineTransformRotate(sender.view.transform,
sender.rotation);
_currRotation = _currRotation + sender.rotation;
[sender setRotation:0];
}
}
I will like to understand what would be the right way to handling rotation with-in the scroll view and it maintains that rotation even after zoom events in Scroll View.
Implement the gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: method in your UIGestureRecognizerDelegate, and return all gestures you want to recognize simultaneously. If you still have trouble with it, check out the answers to UIImageView Gestures (Zoom, Rotate) Question.
Good luck!
EDIT: Your comment has me guessing that the issue is that you can only have one transform on at a time, and the scroll view applies a scale transform, replacing the rotation one. You could remove the native zoom recognizer (see this question), or nest another UIView in the scroll view, and apply the rotation transform to that. I like option two, it seems easier. If you go with option one, use CGAffineTransformConcat to apply both the zoom and rotate transformations independently.
Related
I have a UIScrollView inside a UIView. For customized paging purpose I have set the clipping of this UIScrollView to No so the UIScrollView still shows up in Region A and Region B of the UIView.
[ region A [UIScrollView] region B ]
Now I want Region A B to be able to trigger scroll events of the UIScrollView's when touched.. I remember there is an one liner (something like [X addGestureRecognizer ...]) that does the trick but I've forgotten what it is... It would be great if someone can tell me what that is!
If you want to redirect the UIScrollView's scrolling gestures to another view, you can do this:
[paddingView addGestureRecognizer:scrollView.panGestureRecognizer];
This won't let you have a fluid control on your scrolling, like the one you get from the UIScrollView.
However, you can use this code :
UISwipeGestureRecognizer *leftSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(viewSwiped:)];
[leftSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.view addGestureRecognizer:leftSwipeGestureRecognizer];
[leftSwipeGestureRecognizer release];
UISwipeGestureRecognizer *rightSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(viewSwiped:)];
[rightSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionRight];
[self.view addGestureRecognizer:rightSwipeGestureRecognizer];
[rightSwipeGestureRecognizer release];
firing this method :
- (void)viewSwiped:(UISwipeGestureRecognizer *)swipeGestureRecognizer {
switch (swipeGestureRecognizer.direction) {
case UISwipeGestureRecognizerDirectionLeft:
// Make your scroll view scroll
break;
case UISwipeGestureRecognizerDirectionRight:
// Make your scroll view scroll
break;
default:
// Do Nothing
break;
}
}
It will allow you to retrieve the swipes gestures and page your UIScrollView accordingly.
Two other solutions might be better :
You could use the UIPanGestureRecognizer the same way as above. It recognizes real pan gestures and not only discrete swipes but the implementation of the sync between your finger and the UIScrollView will be slightly more complicated.
You could let your UIScrollView take the total width of the UIView and manage the paging by yourself, recalculating your steps while tracking the current state of the UIScrollView via UIScrollViewDelegate. Again, a bit harder.
Hope this will help,
I'm implementing a drag/drop/resize/rotate labels within my app. So far everything is working except for the UIRotationGestureRecognizer gesture. More specifically, it does not work with the UIPinchGestureRecognizer gesture.
Normally the two gestures compete for two finger touches, so I'm running them in parallel. Below are my 2 methods that the gesture recognizers invoke.
When doing the rotation gesture, the view spins wildly around it's center, with it's height and width changing as follows: height becomes width, width slowly turns into height. Eventually, the view disappears.
Within the view, I have another auto-resizing view. Normally, the pinch gesture automatically resizes the subviews as well, but in this case the subviews with autoresizing masks disappear. The subviews have height and width springs and left/top strut.
What am I doing wrong? How can I both resize and scale a UIView with gestures?
All the delegate methods and connections are setup properly. I need to understand how to handle the order in which the recognizers would be applying the scaling and rotation.
//makes 2 gesture recognizers behave together
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
- (IBAction)handleRotationFrom:(id)sender {
NSLog(#"Gesture rotation %.1f", rotationGestureRecognizer.rotation);
//attempt to continuously rotate the label, starting with a remembered rotation
float rotation = atan2(activeCompanionLabelView.transform.b, activeCompanionLabelView.transform.a);
NSLog(#"existing rotation %.1f", rotation);
// rotation = rotation<0?(2*M_PI)-fabs(rotation):rotation;
rotation +=rotationGestureRecognizer.rotation;
NSLog(#"*** gesture rotation %.1f sum: %.1f, saved: %.1f",rotationGestureRecognizer.rotation, rotation, activeCompanionLabelView.savedRotation);
activeCompanionLabelView.transform = CGAffineTransformMakeRotation((rotation));
activeCompanionLabelView.savedRotation = rotation;
}
- (IBAction)handlePinch:(id)sender {
NSLog(#"pinch %.2f", pinchGestureRecognizer.scale);
//resize, keeping the origin where it was before
activeCompanionLabelView.frame = CGRectMake(activeLabelContainerFrame.origin.x, activeLabelContainerFrame.origin.y, activeLabelContainerFrame.size.width*pinchGestureRecognizer.scale, activeLabelContainerFrame.size.height*pinchGestureRecognizer.scale);
}
If you want two gestureRecognisers to run in parallel (simultaneously) your
view should implement <UIGestureRecognizerDelegate>.
Also, you should make it a delegate of both gestureRecognizers.
rotationGestureRecognizer.delegate=self;
pinchGestureRecognizer.delegate=self;
And you also should implement shouldRecognizeSimultaneouslyWithGestureRecognizer: method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
NOTE: if you have more then this two gestureRecognisers in your view you're gonna have to add some identity checking in this method.
EDIT:
Just found Ole Begemann's article on this topic: Gesture Recognition on iOS with Attention to Detail
I am having an horizontal scrollview in an UIViewController, where i have many images in small sizes. I am keeping the images in scrollview because the images are more, so user can scroll horizontally and choose images. But, the problem is, i have to select an image and drag and drop to that UIViewController view. But, since the images are in scrollview, drag and drop images into UIViewcontroller's view is not working, not detecting the touch events too.
Please NOTE: If i don't have scrollview but just keeping the images also into UIViewcontroller's view itself, drag and drop the images on the same screen, is working very well.
How can I resolve this when I need to have scrollview and drag and drop images, any advice/help please?
Hi Getsy,
I am not going to provide you code directly but give idea how to manage this.
You can manage this way, When you get touch on your object in scrollView at that time or when you move that object by draging at that time disable scroll by myScroll.scrollEnabled = NO;
Then When on endTouch you can enable Scroll by myScroll.scrollEnabled = YES; So by this you can manage you object moving in scroll hope you got logic.
Here is the demo code : Drag and Drop with ScrollView. which has same logic of Disabling scroll view on touchesMoved: and Enabling scroll view on touchesEnded:.
I did implement that behaviour before without any subclassing.
I used canCancelContentTouches = NO of the UIScrollView to make sure the subviews handle there touches on their own. If a subview (in your case an image) was touched, i moved the view out of the scrollview onto the superview and started tracking it's dragging. (You have to calculate the correct coordinates within the new superview, so it stays in place).
After dragging finishes, i checked if the target area was reached, otherwise I moved it back into the scrollview. If that's not detailed enough I could post some code.
Well here is my example code: Github: JDDroppableView
Getsy,
Try the code for drag and drop the objects :
-(void)dragAndDropWithGesture {
UILongPressGestureRecognizer *downwardGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(dragGestureChanged:)];
[scrollViewAlfabeto addGestureRecognizer:downwardGesture];
for (UIGestureRecognizer *gestureRecognizer in myscrollView.gestureRecognizers)
{
[gestureRecognizer requireGestureRecognizerToFail:downwardGesture];
}
}
- (void) dragGestureChanged:(UIPanGestureRecognizer*)gesture
{
CGPoint point = [gesture locationInView:scrollViewAlfabeto];
if (gesture.state == UIGestureRecognizerStateBegan)
{
[imageViewToMove removeFromSuperview];
[self.view addSubview:imageViewToMove];
UIView *draggedView = [myscrollView hitTest:point withEvent:nil];
if ([draggedView isKindOfClass:[UIImageView class]])
{
imageViewToMove = (UIImageView*)draggedView;
}
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
imageToMove.center = point;
}
else if (gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateCancelled ||
gesture.state == UIGestureRecognizerStateFailed)
{
// Determine if dragged view is in an OK drop zone
// If so, then do the drop action, if not, return it to original location
NSLog(#"point.x final:%f", point.x);
NSLog(#"point.y final:%f", point.y);
if (CGRectContainsPoint(goal.frame, point)){
imageToMove.frame = CGRectMake(167, 159, 100, 100);
}
else{
[imageToMove removeFromSuperview];
[myscrollView addSubview:imageToMove];
[imageToMove setFrame:CGRectMake(12, 38, 100, 100)];
imageToMove = nil;
}
}
}
May this code will help you out.
For a similar problem, I made UIScrollView subclass like the following: PoliteScrollView passes touch messages to it's subviews when it determines that they are being dragged.
#interface PoliteScrollView : UIScrollView
// number of pixels perpendicular to the scroll views orientation to make a drag start counting as a subview drag
// defaults to 1/10 of the scroll view's width or height, whichever is smaller
#property(nonatomic,assign) CGFloat subviewDraggingThreshold;
// yes when a subview is being dragged
#property(nonatomic,assign) BOOL isDraggingSubview;
// the subview being dragged
#property(nonatomic,strong) UIView *draggingSubview;
#end
#protocol PoliteScrollViewDelegate <NSObject>
#optional
- (void)scrollView:(PoliteScrollView *)scrollView subviewTouchesBegan:(NSSet *)touches;
- (void)scrollView:(PoliteScrollView *)scrollView subviewTouchesMoved:(NSSet *)touches;
- (void)scrollView:(PoliteScrollView *)scrollView subviewTouchesEnded:(NSSet *)touches;
#end
The key design idea is that dragging in a scroll view is ambiguous. Is the user scrolling or dragging a subview? PoliteScroll view handles this by providing two things: (1) a notion of orientation (horizontal if it's longer than it is wide, vertical otherwise), and (2) a threshold distance for what constitutes a drag in the direction perpendicular to it's orientation. (defaults to 1/10 the width or height).
I pasted this and several other files are in a paste bin, containing the following:
PoliteScrollView .h and .m
DraggableImageView.h and .m - that changes it's position when it gets touch messages.
ViewController.m - that demonstrates thetwo in combination.
To combine these in a project, paste the paste bin into files appropriately named, add a storyboard with a PoliteScrollView (be sure to set it's delegate), add in some images (the ViewController tries to add puppy0.jpeg to puppy4.jpeg.
I created an example which illustrates how to drag and drop between two or more views:
http://www.ancientprogramming.com/2012/04/05/drag-and-drop-between-multiple-uiviews-in-ios/
I found it a good idea to register the gesture recognizer to a different view than the actual views being dragged. This will make sure the gestures continue even though the dragged view changes its 'parent' view.
Maybe it can give some inspiration
I'm looking at some existing code that adds a pinchGestureRecognizer at a specific zoom level of a scrollView. (Like when the scrollView.zoomScale > 10). At this zoom level, the pinch gesture does some special handling with a (handlePinch: selector)on the scrollView on the pinch.
I'm tasked to have a slider emulate the zooming of the scrollView so the user doesn't have to use two fingers to pinch all the time to zoom. I'd like to add the pinchGesture when my zoomScale is > 10 for the scrollView to get the same special handling. I don't want to add two of the same gestureRecognizers since I am assuming that if I blindly add it when the zoomScale > 10, I'm going to get unwanted behavior. I don't know how to check for a specific gesture in this case.
I basically want to do something like this:
- (IBAction)sliderChanged:(id)sender {
UISlider *slider = (UISlider *)sender;
if (slider.value > .6 && slider.value < .8) {
// check for pinch gesture
// I thought I could get the NSArray of gestures from my self.scrollView and check if it's empty, but there are other gestures are already attached to the scrollView.
// I thought I could also try self.scrollView respondsToSelector:#selector(handlePinch:), but I don't think that works.
//self.scrollView addGestureRecognizer
}
if (yourPinchRecognizer == nil) {
// do something
}
This has worked for me with the app I am working on.
Is it possible to pan a UIImageView and when it intersects with the frame of another UIImageView, have that other UIImageView be pushed around by the other UIImageView without just passing over it? I hope that makes sense!
If it is possible, could you give me some ideas on how I'd go about implementing this?
I imagine it would go something like...
If frame intersects frame from left or right on the x/y axis, have the other frame move in that same direction with same distance as the pushing frame. While that logic somewhat makes sense to me, I'm not sure how I'd implement that in code.
I'd really appreciate any advice you can offer.
So, you're only concerned with left / right panning...
Let's assume you have 2 UIImageViews, a & b, as subviews of some other UIView and defined as class members of some class.
You can get help detecting panning gestures (dragging touches) on each of these views by creating instances of UIPanGestureRecognizer and adding them to each UIImageView using method addGestureRecognizer:
When creating each UIPanGestureRecognizer, you need to designate a selector to receive the gesture events. Let say it's called didMove:
Now, some sample code:
- (void) didMove:(UIPanGestureRecognizer*)recognizer {
UIView* view = [recognizer view];
// Remember our UIImageViews are class members called, a & b
//
UIView* otherView = view == a ? b : a;
if (recognizer.state == UIGestureRecognizerStateBegin
|| recognizer.state == UIGestureRecognizerStateChanged) {
// Moving left will have a negative x, moving right positive
//
CGPoint translation = [recognizer translationInView:view.superview];
view.center = CGPointMake(view.center.x + translation.x, view.center.y);
// Reset so we always track the translation from the current view position
//
[recognizer setTranslation:CGPointZero inView:view.superview];
if (CGRectIntersectsRect(view.frame, otherView.frame)) {
// Translate by the same number of points to keep views in sync
//
otherView.center = CGPointMake(otherView.center.x + translation.x, otherView.center.y);
}
}
}
It may not be exactly what you need, but it should give you a good idea how it could be done.
Sounds like you'd better use something like Cocos2D with it's sprites, intersections and some basic physics...
You can do it like this,
First, Create an UIImageView and assign an UIImage to it.
Second, When the user swipes or makes any gesture just change the coordinates of the card by setting its frame again.
Since u want the card to slide away, you have to change the x coordinate to a negative value such that it slides off from the view.
The above said code of reframing the imageview should be given within a UIView setAnimation group of methods...with a required delay.
To achieve the push effect have a method called within the the same animation block of codes which creates a new imageview (having same name as that of first imageview) and keep it out of the view. That is beyond the width of the screen. (dont forget to release the card which was pushes away previously)
Then subsequently again make a new frame for this imageview such that it appears in the centre of the screen....When above three steps of code are put within the animation block of code for UIView or UIImageView with a delay time , u will achieve the required effect.
Hope this helps....
happy coding....