Rotation Gesture on UIScrollView - objective-c

I am working on Scroll View with gestures. I added a UIView in Scroll View whose size is equal to the ScrollView content size. I want to apply the pinch gesture and rotate gesture on the View which is subview of ScrollView. I have done the work of the pinch gesture by using zoom property and delegate of the ScrollView which give me same effect which I want. But Rotation Gesture is creating Problem. When I add rotation gesture on the view then zooming of the scroll view also get disturb.
So how can i apply the pinch gesture and Rotate gesture on the Scroll View's subview whose size must be equal to the content size of the ScrollView initially.
Can anybody give me the way to do this!
This is the code of .m file, when we rotate the view it become invisible
#import "ViewController.h"
#interface ViewController ()
{
UIView *backgroundView;
UIScrollView *scrollView;
CGFloat lastRotation;
}
#end
#implementation ViewController
-(void)loadView
{
[super loadView];
//Scroll View
scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];
scrollView.contentSize = self.view.frame.size;
scrollView.delegate = self;
scrollView.backgroundColor = [UIColor grayColor];
//Zooming factors of the Scroll View
scrollView.minimumZoomScale = 1.0;
scrollView.maximumZoomScale = 5.0f;
scrollView.zoomScale = 1.0;
[self.view addSubview:scrollView];
//Scroll View's subview
backgroundView = [[UIView alloc] initWithFrame:scrollView.frame];
[backgroundView setBackgroundColor:[UIColor orangeColor]];
[scrollView addSubview:backgroundView];
UIRotationGestureRecognizer *bgRotationGstr = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotateBackgroundView:)];
bgRotationGstr.delegate = self;
bgRotationGstr.cancelsTouchesInView = NO;
[backgroundView addGestureRecognizer:bgRotationGstr];
//Child of background view
UIView *childView = [[UIView alloc] initWithFrame:CGRectMake(20, 50, 100, 100)];
childView.backgroundColor = [UIColor grayColor];
[backgroundView addSubview:childView];
}
//Rotation of the background view
-(void)rotateBackgroundView:(UIRotationGestureRecognizer*)gesture
{
CGFloat rotation = 0.0 - (lastRotation - [(UIRotationGestureRecognizer*)gesture rotation]);
CGAffineTransform currentTransform = backgroundView.transform;
CGAffineTransform newTransform = CGAffineTransformRotate(currentTransform,rotation);
[backgroundView setTransform:newTransform];
lastRotation = [(UIRotationGestureRecognizer*)gesture rotation];
if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged)
{
scrollView.scrollEnabled = NO;
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
lastRotation = 0.0;
scrollView.scrollEnabled = YES;
return;
}
}
#pragma mark<UIScrollViewDelegate>
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return backgroundView;
}
#pragma mark<UIGetsureRecognizer>
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return YES;
}
#end

After many days of struggling I found a definitive solution in order to use UIScrollView integrated zoom behavior together with UIRotationGestureRecognizer working like a charm. You have to add a container dummy view as subview of the scroll view, and put the UIImageView as subview of the container view. Afterthat, return the container view in the viewForZoomingInScrollView method and add the UIRotationGestureRecognizer to the scrollview, applying CGAffineTransformRotate to the UIImageView. Finally, return true in the shouldRecognizeSimultaneouslyWithGestureRecognizer method. In this way the scrollView will capture both the two fingers rotation gesture and the pinch to zoom gesture: the zoom will be applied to the dummy view and rotation to the uiimageview, without conflicts between transformations.
In code: let's think to have a UIViewController presenting a UIScrollView. We want to use scrollview's zoom behaviour out of the box together with UIImageView rotation.
1) The controller (or any other object) containing UIScrollView must conforms to UIGestureRecognizerDelegate protocol.
In myViewController.h
#interface myViewController : UIViewController < UIGestureRecognizerDelegate> {
}
2) Create a UIScrollView, add a dummy view as subview and finally add a UIImageView as subview of the dummy view.
In myViewController.m
//Scrollview
myScrollView=[[UIScrollView alloc] initWithFrame:CGRectMake(0,0, view.frame.size.width, view.frame.size.height)];
myScrollView.delegate=self;
[view addSubview:myScrollView];
//Dummy View
UIView *dummyView=[[UIView alloc] initWithFrame:CGRectMake(0, 0, myScrollView.frame.size.width, myScrollView.frame.size.height)];
[self addSubview:dummyView];
//ImageView
imageView=[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, dummyView.frame.size.width, dummyView.frame.size.height)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
[dummyView addSubview:imageView];
//Add rotation gesture to the scrollView
rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(handleRotate:)];
[myScrollView addGestureRecognizer:_rotationGestureRecognizer];
//Set the controller as delegate of the recognizer
rotationGestureRecognizer.delegate=self;
[...]
#pragma UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
//Set the dummy view (imageview's superview) as view for zooming
return imageView.superview;
}
[...]
#pragma Mark - UIGestureRecognizerDelegate
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
//Make it possibile to recognize simultaneously pinch and rotation gestures
return TRUE;
}
[...]
- (IBAction) handleRotate:(UIRotationGestureRecognizer*)recognizer {
//Apply the rotation to imageView
imageView.transform = CGAffineTransformRotate(imageView.transform, recognizer.rotation);
recognizer.rotation = 0;
}
For simplyfing purposes, I wrote everything in the same controller. You are free to subclass the UIScrollView. Remember that the tricks are:
1) returning the container dummy view as viewForZoomingInScrollView so zoom will affect the container view and rotation will affect the uiimageview.
2) set the viewcontroller containing the scrollview as delegate of the rotation gesture recognizer and return TRUE for shouldRecognizeSimultaneouslyWithGestureRecognizer.

Add this to your .m
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
Also make sure you dont have exclusive touch enabled on any gesture recognizers.

Try to put you scalable/rotatable content in a subview of your content view. ( Maybe you must set the "clip content" property of your content view to true - not sure )
This way the scrollview is not concerned anymore by transformations, since its content view stays still.
If you have to display the clipped content ( if you rotate a square, for example, the corners go out the initial area), recompute your content view and update the scrollview.
Since you gave the code, I suggest to try:
//Scroll View's subview
backgroundViewHolder = [[UIView alloc] initWithFrame:scrollView.frame];
[backgroundViewHolder setBackgroundColor:[UIColor orangeColor]];
[scrollView backgroundViewHolder];
//Holder View's subview
backgroundView = [[UIView alloc] initWithFrame:backgroundViewHolder.bounds];
[backgroundViewHolder addSubview:backgroundView];
Everything else should remain the same. This is just an idea... Not sure it is the right answer.

Add this to your implement file, make it a UIGestureRecognizerDelegate.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
rotationGeustureRecognozier.delegate = self;// (the implement file)

Related

Tracking frame.origin.x in UIPageController while scrolling

I'm using standard UIPageViewController here and I need to track x coordinate in order to change the alpha of the image accordingly. I try to find the UIScrollView in UIPageViewController like this and it works:
for (UIView *view in self.pageViewController.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
[(UIScrollView *)view setDelegate:self];
}
}
But when I do this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
MTWPageContentViewController *pageContentVC = [self viewControllerAtIndex:_mainPageControl.currentPage];
NSLog(#"%f", pageContentVC.view.frame.origin.x);
}
NSLog gives me 0.000 which obviously means that I'm doing something wrong.
pageContentVC exists and it is the view controller visible at the moment I scroll.
So, tell me how do I track x coordinate of UIPageViewController's subview while scrolling?
You can grab the gesture recognisers from the UIPageViewController and add yourself as a target/action to get information about the pan.
for (UIGestureRecognizer *gestureRecognizer in self.pageViewController.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]) {
[gestureRecognizer addTarget:self action:#selector(panned:)];
}
}

UIScrollView return to its original place

I have the following code:
float yOffset = activeTextView.frame.origin.y - keyboardSize.height + 55;
CGPoint scrollPoint = CGPointMake(0.0, yOffset);
[scrollView setContentOffset:scrollPoint animated:YES];
This animates the scrollView in - (void)keyboardWasShown:(NSNotification *)notification
I am trying to return the scrollView to it's original location after the hiding the keyboard like this:
- (void) keyboardWillHide:(NSNotification *)notification {
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
}
But it doesn't work!
How can I return the UIScrollView and actually the whole screen to its original location so the user will see what he saw before animation of the scrollview?
In your keyboardWasShown: method, you're setting the contentOffset property ([scrollView setContentOffset:] is equivalent to scrollView.contentOffset). However, in keyboardWillHide:, you're setting contentInset, which is something completely different (essentially, it's the amount of internal padding of the scroll view's content). Try
scrollView.contentOffset = CGPointZero; // non-animated by default
or
[scrollView setContentOffset:CGPointZero animated:YES]; // animated
Also, as NSResponder mentioned, make sure your keyboardWillHide: method is being called.

MKMapView and the responder chain

I have an MKMapView with a single subview:
MKMapView *mapView = [[MKMapView alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(0, 200, 200, 200)];
subView.backgroundColor = [UIColor grayColor];
[mapView addSubview:subView];
I would expect that because the subview does not handle any touch events, all touch events would be passed along to the parent map view (via the responder chain). I would then expect that panning and pinching in the subview would pan and pinch the map.
This, unfortunately, does not appear to be the case. Does anyone know of a way to get the map view in to the responder chain?
I realize overriding hitTest in my subview can achieve what I'm expecting here, but I can't use that approach because I have other gestures I need to respond to in the subview.
How about handling all gestures with UIGestureRecognizers (properly setup to ignore other gesture recognizer or to fire simultanously with them) added to your mapView and disabling userInteractionEnabled of your subview?
I use the following code to listen to Taps on the mapView without interfering with the standard gestures:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(mtd_handleMapTap:)];
// we require all gesture recognizer except other single-tap gesture recognizers to fail
for (UIGestureRecognizer *gesture in self.gestureRecognizers) {
if ([gesture isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *systemTap = (UITapGestureRecognizer *)gesture;
if (systemTap.numberOfTapsRequired > 1) {
[tap requireGestureRecognizerToFail:systemTap];
}
} else {
[tap requireGestureRecognizerToFail:gesture];
}
}
- (void)mtd_handleMapTap:(UITapGestureRecognizer *)tap {
if ((tap.state & UIGestureRecognizerStateRecognized) == UIGestureRecognizerStateRecognized) {
// Get view frame rect in the mapView's coordinate system
CGRect viewFrameInMapView = [self.mySubview.superview convertRect:self.mySubview.frame toView:self.mapView];
// Get touch point in the mapView's coordinate system
CGPoint point = [tap locationInView:self.mapView];
// Check if the touch is within the view bounds
if (CGRectContainsPoint(viewFrameInMapView, point)) {
// tap was on mySubview
}
}
}

IOS Managing the Keyboard - Move UIScrollView Content up

I am using the example in the IOS Developer Library for managing the keyboard.
Text, Web and Edition Programming Guide
I have created a view using the IB. Its a simple UI that has a UIScrollView, UITextView, UIButton, and a UITextField. I placed the UIScrollView on my view and then added all the other controls as children of this scrollview. The scrollview is exposed to the viewcontroller via a IBOutlet "scrollView".
The follow code executes with the user sets focus to the textField but I never see a scrollbar appear and the scrollbar's contents are not moved. Should I be able to see the scrollbar by default? Can someone tell me what I'm missing?
-(void) keyboardWasShown:(NSNotification *)aNotification{
NSDictionary * info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
// Your application might not need or want this behavior.
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
if (!CGRectContainsPoint(aRect, activeField.frame.origin) ) {
CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y-kbSize.height);
[scrollView setContentOffset:scrollPoint animated:YES];
}
}
Again, I'm taking this code directly from the IOS Programming guide in the link. The UI layout looks like a basic chat window. I would like to move the "input" field up while the soft keyboard is visible. Thanks!
Update
It seems that I needed to add some padding to actually see the controls located at the bottom of the scrollview.
CGPoint scrollPoint = CGPointMake(0.0, (activeField.frame.origin.y - kbSize.height) + 10.0);
How come I don't see scrollbars?
IF YOU HAVE PARENT VIEW AS SCROLL THEN JUST USE : UITextFielddelegate and set the methods
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
if(textField == self.txtUserName)
{
[self.txtPassword becomeFirstResponder];
}
else if(textField == self.txtPassword){
[self.txtPassword resignFirstResponder];
CGPoint bottomOffset = CGPointMake(0, 0);
[scrView setContentOffset:bottomOffset animated:YES];
[textField resignFirstResponder];
}
return YES;
}
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
if (textField == self.txtUserName)
{
CGPoint bottomOffset = CGPointMake(0, 80);
[scrView setContentOffset:bottomOffset animated:YES];
}
if (textField == self.txtPassword)
{
CGPoint bottomOffset = CGPointMake(0, 135);
[scrView setContentOffset:bottomOffset animated:YES];
}
}
Scrollbars should show up only on user interaction. That's not the case here since your programmatically setting the inset of your scrollview.
If you want to show the scroll bars, i believe UIScrollView defines a flashScrollIndicators method, that should show the scroll bars.

detect long press on UINavigationItem's back button

I want to add functionality to my back buttons through my UINavigationController-based app where long-pressing the back button will pop to root. However, I can't figure out where to attach the gesture recognizer. Do I subclass UINavigationBar and try and detect if the long press is in the left button region?
I've heard of people adding similar functionality before. Anyone have any ideas?
I know this question is old, but I came up with a solution. Instead of trying to add the gesture recognizer to the button itself (which would be ideal), I added it to the self.navigationController.navigationBar and then in the action method, use the locationInView to see if I'm over the back button. I wasn't entirely sure about how to identify the back button precisely, so I'm clumsily just grabbing the the first subview with an x coordinate less than some arbitrary value, but it seems promising. If someone has a better way to identify the frame of the back button, let me know.
- (void)longPress:(UILongPressGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
// set a default rectangle in case we don't find the back button for some reason
CGRect rect = CGRectMake(0, 0, 100, 40);
// iterate through the subviews looking for something that looks like it might be the right location to be the back button
for (UIView *subview in self.navigationController.navigationBar.subviews)
{
if (subview.frame.origin.x < 30)
{
rect = subview.frame;
break;
}
}
// ok, let's get the point of the long press
CGPoint longPressPoint = [sender locationInView:self.navigationController.navigationBar];
// if the long press point in the rectangle then do whatever
if (CGRectContainsPoint(rect, longPressPoint))
[self doWhatever];
}
}
- (void)addLongPressGesture
{
if (NSClassFromString(#"UILongPressGestureRecognizer"))
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.navigationController.navigationBar addGestureRecognizer:longPress];
[longPress release];
}
}
I believe UIGestureRecognizers can only be added to UIViews and subclasses of UIViews.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIView_Class/UIView/UIView.html
The back button is a UIBarButtonItem that descends from NSObject. Therefore, you won't be able to attach a gesture recognizer to a standard back button using
UILongPressGestureRecognizer *longPressGesture =
[[[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(longPress:)] autorelease];
[self.navigationItem.backBarButtonItem addGestureRecognizer:longPressGesture];
You can however add a custom view to a UIBarButtonItem. A custom view could just as easily be a UIView, UIButton, UILabel, etc.
Example:
UIView *myTransparentGestureView = [[UIView alloc] initWithFrame:CGRectMake(0,0,40,30)];
[myTransparentGestureView addGestureRecognizer:longPressGesture];
[self.navigationItem.backBarButtonItem setCustomView:myTransparentGestureView];
// Or you could set it like this
// self.navigationItem.backBarButtonItem.customView = myTransparentGestureView;
[myTransparentGestureView release];
You have to be careful however, since setting properties on backBarButtonItem applies to the next view that you push. So if you have view A that pushes to view B and you want the gesture to be recognized when you tap back in view B. You must set it up in view A.
I followed a slightly different path, figured I'd share it. The above answers are fine, but really, if the long press is in the leading 1/3 of the nav bar, that's good enough for me:
- (void)longPress:(UILongPressGestureRecognizer *)gr
{
NSLog(#"longPress:");
UINavigationBar *navBar = [self navigationBar];
CGFloat height = navBar.bounds.size.height;
CGPoint pt = [gr locationOfTouch:0 inView:navBar];
//NSLog(#"PT=%# height=%f", NSStringFromCGPoint(pt), height);
if(CGRectContainsPoint(CGRectMake(0,0,100,height), pt)) {
[self popToViewController:self.viewControllers[0] animated:YES];
}
}
Here's my solution:
In appDelegate (the "owner" of the nav bar in my app), In applicationDidFinishLaunchingWithOptions:
Get the nav bar view and add the gesture recognizer to the whole view:
// Get the nav bar view
UINavigationBar *myNavBar = nil;
for (UIView *view in [self.window.rootViewController.view subviews]) {
if ([view isKindOfClass:[UINavigationBar class]]) {
NSLog(#"Found Nav Bar!!!");
myNavBar = (UINavigationBar *)view;
}
}
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:#selector(backButtonLongPress:)];
[myNavBar addGestureRecognizer:longPress];
NSLog(#"Gesture Recognizer Added.");
Then in appDelegate, in -(void) backButtonLongPress:(id) sender
Check to see if the gesture occurs within the frame of the back button:
if ([sender state] == UIGestureRecognizerStateBegan) {
// Get the nav bar view
UINavigationBar *myNavBar = nil;
for (UIView *view in [self.window.rootViewController.view subviews]) {
if ([view isKindOfClass:[UINavigationBar class]]) {
NSLog(#"Found Nav Bar!!!");
myNavBar = (UINavigationBar *)view;
}
}
// Get the back button view
UIView *backButtonView = nil;
for (UIView *view in [myNavBar subviews]) {
if ([[[view class] description] isEqualToString:#"UINavigationItemButtonView"]) {
backButtonView = view;
NSLog(#"Found It: %#", backButtonView);
NSLog(#"Back Button View Frame: %f, %f; %f, %f", backButtonView.frame.origin.x, backButtonView.frame.origin.y, backButtonView.frame.size.width, backButtonView.frame.size.height);
}
}
CGPoint longPressPoint = [sender locationInView:myNavBar];
NSLog(#"Touch is in back button: %#", CGRectContainsPoint(backButtonView.frame, longPressPoint) ? #"YES" : #"NO");
if (CGRectContainsPoint(backButtonView.frame, longPressPoint)) {
// Place your action here
}
// Do nothing if outside the back button frame
}