UISplitViewController pan to primary view from anywhere - ios

Sorry for the long-winded explination, but this question - or something similar - has been asked a few times and I havent found a satisfactory answer. I am writing an iPad app in iOS 8 that implements UISplitViewController. Recently I have been attempting to get it to work on the iPhone. It transferred over pretty well, everything collapses automatically and a back button is included in the left side of my nav. bar.
My problem is that I want to keep the back button functionality to pop one view off the stack, but also be able to pan back to the primary view even if there are several detail views on top of it. Ideally, I want to be able to overwrite or redirect the interactivePopGestureRecognizer so that the gesture smoothly pans to the primary view (in some cases it can have anywhere from 1 to 4 detail views stacked on top of it). But, I cannot figure out how to do this.
My current solution (code below) is to disable the interactivePopGestureRecognizer in the detail viewcontroller and implement my own ScreenEdgePanGestureRecognizer that, when triggered, executes popToRootViewController. I've subclassed the ScreenEdgePanGestureRecognizer so it treats the screen edge pan as a discrete "swipe" (i.e. once a large enough screen edge swipe is detected - pop everything off the stack so the primary view is visible).
Code in detail view controller to stop interactivePopGestureRecognizer:
-(void)viewWillAppear : (BOOL) animated {
[super viewWillAppear : animated];
// stops navigation controller from responding to the default back swipe gesture
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled =NO;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
// Disable the default back swipe gesture tied to automatically included back button
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer isEqual:self.navigationController.interactivePopGestureRecognizer]) {
return NO;
} else {
return YES;
}
}
I didn't think it was necessary to include my subclass for the screenEdgePanGestureRecognizer because it has nothing to do with the solution I am asking about here is some pseudocode that shows what my #selector does in the detail viewcontroller:
- (IBAction)leftEdgeSwipe:(ScreenEdgeSwipeGestureRecognizer*)sender {
if (sender.swipeIsValid) {
[(UINavigationController *)self.splitViewController.viewControllers[0]
popToRootViewControllerAnimated:YES];
}
}
I tried to use the continuous pan, but cannot find a way to present the primary view in the background as I am pulling the current view aside to give that clean, smooth panning effect. I am able to make it so I can move the current view around, but there is just a grey background behind it where I would want my primary view to be.
Summation: If there is indeed no way to change the interactivePopGestureRecognizer to always jump to my primary view (ideal solution), then any info on how I can make my own smooth pan back to my primary view would be much appreciated.

So I have been messing around with making a smooth panning gesture subclass. Currently it functions similarly to Apple's back gesture except it jumps all the way back to the root view controller instead of popping one view off the stack. The only problem is that it does not yet show the primary view in the background while panning. I will update the answer once I get that worked out.
Here is the subclass:
#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#import "ScreenEdgeSwipeGestureRecognizer.h"
#interface ScreenEdgeSwipeGestureRecognizer ()
#property (nonatomic) UINavigationController* navController;
#end
#implementation ScreenEdgeSwipeGestureRecognizer{
CGPoint _screenCenter;
CGPoint _cumulativePanDistance;
}
- (id)initWithNavigationController:(UINavigationController*)navController {
self = [super initWithTarget:self action:#selector(leftEdgePan:)];
_screenCenter = CGPointZero;
_cumulativePanDistance = CGPointZero;
self.edges = UIRectEdgeLeft;
self.navController = navController;
return self;
}
- (IBAction)leftEdgePan:(ScreenEdgeSwipeGestureRecognizer*)sender {
assert(sender == self);
switch (self.state) {
case UIGestureRecognizerStateBegan:
[self initializePositions];
break;
case UIGestureRecognizerStateChanged:
[self updatePositions];
break;
case UIGestureRecognizerStateEnded:
[self animateViewBasedOnCurrentLocation];
break;
case UIGestureRecognizerStateCancelled:
[self animateViewToCenter];
break;
default:
break;
}
// Reset velocity of the pan so current velocity does not compound with velocity of next cycle
[sender setTranslation:CGPointMake(0, 0) inView:sender.view];
}
- (void)initializePositions {
_screenCenter = self.view.center;
_cumulativePanDistance = CGPointZero;
}
- (void)updatePositions {
// Track position of user touch event
CGPoint deltaSinceLastCycle = [self translationInView:self.view];
// View center = view center at last cycle + distance moved by user touch since last cycle
self.view.center=CGPointMake((self.view.center.x + deltaSinceLastCycle.x), self.view.center.y+ 0);
// Update the total positive distance traveled by the user touch event.
_cumulativePanDistance.x = _cumulativePanDistance.x + deltaSinceLastCycle.x;
}
- (void)animateViewBasedOnCurrentLocation {
if (_cumulativePanDistance.x >= (_screenCenter.x - 50)){
[self reset];
[_navController popToRootViewControllerAnimated:YES];
}else{
[self animateViewToCenter];
[self reset];
}
}
- (void)animateViewToCenter {
[UIView animateWithDuration:0.25 animations:^{self.view.center = self->_screenCenter;}];
}
- (void)reset {
[super reset];
_cumulativePanDistance = CGPointZero;
self.state = UIGestureRecognizerStatePossible;
}
#end
Here is how I instantiate the recognizer in my view controller:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Initialize the screen edge pan gesture recognizer.
_masterNavigationController = self.splitViewController.viewControllers[0];
ScreenEdgePanGestureRecognizer* edgePanRecognizer = [[ScreenEdgeSwipeGestureRecognizer alloc] initWithNavigationController:_masterNavigationController];
// Add recognizer to view this controller is bound to.
[self.view addGestureRecognizer:_edgePanRecognizer];
}

Related

iOS - Switch between dismiss and scroll gestures

There's a behavior in the Line messenger app (the de facto messenger app in Japan) that I'm trying to emulate.
Basically, they have a modal view controller with a scroll view inside. When the scroll action reaches the top of its content, the view controller seamlessly switches to an interactive dismissal animation. Also, when the gesture returns the view to the top of the screen, control is returned to the scroll view.
Here's a gif of how it looks.
For the life of me, I can't figure out how they did it. I've tried a few different methods, but they've all failed, and I'm out of ideas. Can anyone point me in the right direction?
EDIT2
To clarify, the behavior that I want to emulate isn't just simply dragging the window down. I can do that, no problem.
I want to know how the same scroll gesture (without lifting the finger) triggers the dismissal transition and then transfers control back to the scroll view after the view has been dragged back to the original position.
This is the part that I can't figure out.
End EDIT2
EDIT1
Here's what I have so far. I was able to use the scroll view delegate methods to add a target-selector that handles the regular dismissal animation, but it still doesn't work as expected.
I create a UIViewController with a UIWebView as a property. Then I put it in a UINavigationController, which is presented modally.
The navigation controller uses animation/transition controllers for the regular interactive dismissal (which can be done by gesturing over the navigation bar).
From here, everything works fine, but the dismissal can't be triggered from the scroll view.
NavigationController.h
#interface NavigationController : UINavigationController <UIViewControllerTransitioningDelegate>
#property (nonatomic, strong) UIPanGestureRecognizer *gestureRecog;
- (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer;
#end
NavigationController.m
#import "NavigationController.h"
#import "AnimationController.h"
#import "TransitionController.h"
#implementation NavigationController {
AnimationController *_animator;
TransitionController *_interactor;
}
- (instancetype)init {
self = [super init];
self.transitioningDelegate = self;
_animator = [[AnimationController alloc] init];
_interactor = [[TransitionController alloc] init];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Set the gesture recognizer
self.gestureRecog = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[self.view addGestureRecognizer:_gestureRecog];
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator {
if (animator == _animator && _interactor.hasStarted) {
return _interactor;
}
return nil;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
if (dismissed == self || [self.viewControllers indexOfObject:dismissed] != NSNotFound) {
return _animator;
}
return nil;
}
- (void)handleGesture:(UIPanGestureRecognizer *)gestureRecog {
CGFloat threshold = 0.3f;
CGPoint translation = [gestureRecog translationInView:self.view];
CGFloat verticalMovement = translation.y / self.view.bounds.size.height;
CGFloat downwardMovement = fmaxf(verticalMovement, 0.0f);
CGFloat downwardMovementPercent = fminf(downwardMovement, 1.0f);
switch (gestureRecog.state) {
case UIGestureRecognizerStateBegan: {
_interactor.hasStarted = YES;
[self dismissViewControllerAnimated:YES completion:nil];
break;
}
case UIGestureRecognizerStateChanged: {
if (!_interactor.hasStarted) {
_interactor.hasStarted = YES;
[self dismissViewControllerAnimated:YES completion:nil];
}
_interactor.shouldFinish = downwardMovementPercent > threshold;
[_interactor updateInteractiveTransition:downwardMovementPercent];
break;
}
case UIGestureRecognizerStateCancelled: {
_interactor.hasStarted = NO;
[_interactor cancelInteractiveTransition];
break;
}
case UIGestureRecognizerStateEnded: {
_interactor.hasStarted = NO;
if (_interactor.shouldFinish) {
[_interactor finishInteractiveTransition];
} else {
[_interactor cancelInteractiveTransition];
}
break;
}
default: {
break;
}
}
}
#end
Now, I have to get that gesture handling to trigger when the scroll view has reached the top. So, here's what I did in the view controller.
WebViewController.m
#import "WebViewController.h"
#import "NavigationController.h"
#interface WebViewController ()
#property (weak, nonatomic) IBOutlet UIWebView *webView;
#end
#implementation WebViewController {
BOOL _isHandlingPan;
CGPoint _topContentOffset;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.webView.scrollView setDelegate:self];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if ((scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan ||
scrollView.panGestureRecognizer.state == UIGestureRecognizerStateChanged) &&
! _isHandlingPan &&
scrollView.contentOffset.y < self.navigationController.navigationBar.translucent ? -64.0f : 0) {
NSLog(#"Adding scroll target");
_topContentOffset = CGPointMake(scrollView.contentOffset.x, self.navigationController.navigationBar.translucent ? -64.0f : 0);
_isHandlingPan = YES;
[scrollView.panGestureRecognizer addTarget:self action:#selector(handleGesture:)];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
NSLog(#"Did End Dragging");
if (_isHandlingPan) {
NSLog(#"Removing action");
_isHandlingPan = NO;
[scrollView.panGestureRecognizer removeTarget:self action:#selector(handleGesture:)];
}
}
- (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer {
[(NavigationController*)self.navigationController handleGesture:gestureRecognizer];
}
This still doesn't work quite right. Even during the dismissal animation, the scroll view is still scrolling with the gesture.
End EDIT1
That is a custom interactive transition.
First, you need set transitioningDelegate of UIViewController
id<UIViewControllerTransitioningDelegate> transitioningDelegate;
Then implment these two method to
//Asks your delegate for the transition animator object to use when dismissing a view controller.
- animationControllerForDismissedController:
//Asks your delegate for the interactive animator object to use when dismissing a view controller.
- interactionControllerForDismissal:
When drag to top, you start the transition, you may use UIPercentDrivenInteractiveTransition to control the progress during scrolling.
You can also refer to the source code of ZFDragableModalTransition
Image of ZFDragableModalTransition
As explained here the solution is quite complex. The person who answered, #trungduc, programmed a little demo published on github doing the sought behaviour. You can find it here.
The easiest way of making this work is to copy the 4 files found in /TestPanel/Presentation/ in the attached github repository, to your project. Then add the PanelAnimationControllerDelegate to your View Controller containing the scroll view (i.e. using the protocol).
Add the following to your View Controller, to satisfy the protocol:
func shouldHandlePanelInteractionGesture() -> Bool {
return (scrollView.contentOffset.y == 0);
}
Add this to deactivate the bouncing effect at the top:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.bounces = (scrollView.contentOffset.y > 10);
}
Set scrollView.delegate = self
Before presenting your View Controller containing the scroll view set the following propreties to your View Controller:
ScrollViewController.transitioningDelegate = self.panelTransitioningDelegate
ScrollViewController.modalPresentationStyle = .custom
If you want to change the size of your ScrollViewController, you will need to comment out the override of the frameOfPresentedViewInContainerView in the PanelPresentationController file (one of the 4). Then in the presentationTransitionWillBegin method, you will need to set let frameOfPresentedViewInContainerView = self.frameOfPresentedViewInContainerView.insetBy(dx: 0, dy: 20) with the wanted inset of dx and dy.
Thank you to trungduc for this amazing solution!!

Page Viewer with Side Menu (Navigation drawer)

I am using REFrostedViewController as a side menu controller, the problem is this menu will appear from any point of screen, I want it to be like the Android version, which will appear only if you start swiping from the start left of screen, if swiping from any other point, the menu will not appear.
I want this because I am having a Page viewer inside this menu.
Since I am new in IOS, I looked inside the library code and didn't find any function that define the starting point also tried to modify the message
panGestureRecognized it didn't work .
So I'd like to suggest me a help or any other library that have the option.
If you have follow this Library then i think you have put this code too
In ViewDidLoad
[self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureRecognized:)]];
And
- (void)panGestureRecognized:(UIPanGestureRecognizer *)sender
{
// Dismiss keyboard (optional)
//
[self.view endEditing:YES];
[self.frostedViewController.view endEditing:YES];
// Present the view controller
//
[self.frostedViewController panGestureRecognized:sender];
}
Replace Above code with below code.
Confirm UIGestureRecognizerDelegate Delegate protocol
#interface DEMONavigationController () <UIGestureRecognizerDelegate>
In ViewDidLoad
UIScreenEdgePanGestureRecognizer *leftEdgeGesture = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:#selector(handleLeftEdgeGesture:)];
leftEdgeGesture.edges = UIRectEdgeLeft;
leftEdgeGesture.delegate = self;
[self.view addGestureRecognizer:leftEdgeGesture];}
And
- (void)handleLeftEdgeGesture:(UIScreenEdgePanGestureRecognizer *)gesture {
if(UIGestureRecognizerStateBegan == gesture.state ||
UIGestureRecognizerStateChanged == gesture.state) {
[self.frostedViewController presentMenuViewController];
}else{
// DO nothing
}
}

Detecting a touch anywhere on the screen

I am wanting to know when a user has touched anywhere on the screen of my app.
I have looked into using -(UIResponder *)nextResponder but unfortunately this will not work, as I am also reloaded a table automatically, so this gets trigged when that occurs.
I have also tried a gesture recognizer, with the following code. But this will only recognise touches on the view. Where as I have many buttons the user will be using to operate the app. I would like to avoid adding a gesture recogniser or code for this in every button and segment control I have on the screen
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapOnView:)];
[self.mainView addGestureRecognizer:tap];
- (void)tapOnView:(UITapGestureRecognizer *)sender
{
//do something
}
I have also tried -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event , but this has the same issue as the gesture recognizer.
I was wondering if there is any way I could achieve this task. I was hoping that I may be able to recognise the type of event from within the nextResponder, and then I could detect if it is button for example.
EDIT: The reason I am working on this is that my app needs to stay active and the screen cannot be locked (so I have disabled screen locking). To avoid excessive use of power, I need to dim the screen, but then return the brightness back to the original level once the app is touched. I need this feature to only occur on 1 of my viewcontrollers.
As mentioned by Ian MacDonald, using hitTest:: is a great solution to detect user interaction on an app wide scale, including when buttons, textfields, etc, are selected.
My solution was to subclass UIWindow and implement the hitTest method.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// do your stuff here
// return nil if you want to prevent interaction with UI elements
return [super hitTest:point withEvent:event];
}
You could attach your UITapGestureRecognizer to your [[UIApplication sharedApplication] keyWindow].
Alternatively, you could override hitTest: of your root UIView.
Is there a particular task you are hoping to accomplish? There may be a better way than assigning an "anywhere" gesture.
Edit: Use hitTest:.
#interface PassthroughView : UIView
#property (readonly) id target;
#property (readonly) SEL selector;
#end
#implementation PassthroughView
- (void)setTarget:(id)target selector:(SEL)selector {
_target = target;
_selector = selector;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
[_target performSelector:_selector];
return nil;
}
#end
#implementation YourUIViewController {
PassthroughView *anytouchView;
}
- (void)viewDidLoad {
// Add this at the end so it's above all other views.
anytouchView = [[PassthroughView alloc] initWithFrame:self.view.bounds];
[anytouchView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[anytouchView setTarget:self selector:#selector(undim)];
[anytouchView setHidden:YES];
[self.view addSubview:anytouchView];
}
- (void)undim {
[anytouchView setHidden:YES];
}
- (void)dim {
[anytouchView setHidden:NO];
}
#end
Your edit adds more clarity to your question.
The reason I am working on this is that my app needs to stay active
and the screen cannot be locked (so I have disabled screen locking).
To avoid excessive use of power, I need to dim the screen, but then
return the brightness back to the original level once the app is
touched.
Since you are controlling the screen brightness, you can add one transparent view controller before dimming screen on top of your root controller which does only one job, listen to tap using Tap gesture. And on tap you can dismiss the view controller and adjust brightness to previous state.
By doing so you dont have to worry about buttons being clicked as they will be below the transparent view controller. Since its a whole new view controller sitting on top of stack you dont have to modify your existing code as well.
Ok I have had a similar problem before.
As I remember I subclassed the UIWindow for full screen detection and made it First responder.
Than I overridden the touch to handle from subclasses.
You can also use code to identify the control that is been touched.
#import <QuartzCore/QuartzCore.h>
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setMultipleTouchEnabled:YES];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Enumerate over all the touches
[touches enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
// Get a single touch and it's location
UITouch *touch = obj;
CGPoint touchPoint = [touch locationInView:self.view];
...
}];
}
To disable the locking of screen I used below code:
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
I used following functions to dim or increase the screen brightness
[[UIScreen mainScreen] setBrightness:0.0f]; //and
[[UIScreen mainScreen] setBrightness:1.0f];

Preventing MKMapView changing selection (cleanly)

I have a custom subclass of MKPinAnnotationView that displays a custom call out. I want to handle touch events inside that annotation.
I have a working solution (below) but it just doesn't feel right. I have a rule of thumb that whenever I use performSelector: .. withDelay: I'm fighting the system rather than working with it.
Does anyone have a good, clean workaround for the aggressive event handling of MKMapView and annotation selection handling?
My current solution:
(All code from my annotation selection class)
I do my own hit testing (without this my gesture recognisers don't fire as the Map View consumes the events:
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event; {
// To enable our gesture recogniser to fire. we have to hit test and return the correct view for events inside the callout.
UIView* hitView = nil;
if (self.selected) {
// check if we tpped inside the custom view
if (CGRectContainsPoint(self.customView.frame, point))
hitView = self.customView;
}
if(hitView) {
// If we are performing a gesture recogniser (and hence want to consume the action)
// we need to turn off selections for the annotation temporarily
// the are re-enabled in the gesture recogniser.
self.selectionEnabled = NO;
// *1* The re-enable selection a moment later
[self performSelector:#selector(enableAnnotationSelection) withObject:nil afterDelay:kAnnotationSelectionDelay];
} else {
// We didn't hit test so pass up the chain.
hitView = [super hitTest:point withEvent:event];
}
return hitView;
}
Note that I also turn off selections so that in my overridden setSelected I can ignore the deselection.
- (void)setSelected:(BOOL)selected animated:(BOOL)animated; {
// If we have hit tested positive for one of our views with a gesture recogniser, temporarily
// disable selections through _selectionEnabled
if(!_selectionEnabled){
// Note that from here one, we are out of sync with the enclosing map view
// we're just displaying out callout even though it thinks we've been deselected
return;
}
if(selected) {
// deleted code to set up view here
[self addSubview:_customView];
_mainTapGestureRecogniser = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(calloutTapped:)];
[_customView addGestureRecognizer: _mainTapGestureRecogniser];
} else {
self.selected = NO;
[_customView removeFromSuperview];
}
}
It's the line commented 1 that I don't like but it's also pretty hairy at the end of theta delayed fire. I have to walk back up the superview chain to get to the mapView so that I can convince it the selection is still in place.
// Locate the mapview so that we can ensure it has the correct annotation selection state after we have ignored selections.
- (void)enableAnnotationSelection {
// This reenables the seelction state and resets the parent map view's idea of the
// correct selection i.e. us.
MKMapView* map = [self findMapView];
[map selectAnnotation:self.annotation animated:NO];
_selectionEnabled = YES;
}
with
-(MKMapView*)findMapView; {
UIView* view = [self superview];
while(!_mapView) {
if([view isKindOfClass:[MKMapView class]]) {
_mapView = (MKMapView*)view;
} else if ([view isKindOfClass:[UIWindow class]]){
return nil;
} else{
view = [view superview];
if(!view)
return nil;
}
}
return _mapView;
}
This all seems to work without and downside (like flicker I've seen from other solutions. It's relatively straightforward but it doesn't feel right.
Anyone have a better solution?
I don't think you need to monkey with the map view's selection tracking. If I'm correctly interpreting what you want, you should be able to accomplish it with just the hitTest:withEvent: override and canShowCallout set to NO. In setSelected: perform your callout appearance/disappearance animation accordingly. You should also override setHighlighted: and adjust the display of your custom callout if visible.

How to prevent auto rotation like the iPhone photo app does?

I want to prevent a UICollectionViewController from auto rotation when there's a finger on the screen. The finger could move, the device could rotate, but the UICollectionViewController shouldn't rotate whenever the finger is still on screen.
And the UICollectionViewController should rotate immediately when the finger left the screen. As the iPhone photo app does.
Question:
How to detect touch?
I overwrite the touchBegan:withEvent: etc. in UICollectionView subclass. But when the UICollectionView start scrolling, it calls touchCanceled:withEvent: method.
If I start scrolling the UICollectionView earlier, the touchBegan:withEvent: does even not fired.
How to prevent auto rotation temporarily?
I overwrite the shouldAutorotate in my view controller to prevent rotation. But when the finger left the screen, the UICollectionView can't rotate immediately.
Try this code, please:
#interface BlockAutorotateViewController ()
#property (nonatomic, strong) UILongPressGestureRecognizer *longPressGestureRecognizer;
#property (nonatomic, strong) UISwipeGestureRecognizer *swipeGestureRecognizer;
#property (nonatomic, assign, getter = isPressed) BOOL pressed;
#end
#implementation BlockAutorotateViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.pressed = NO;
self.longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(myLongPressAction:)];
self.longPressGestureRecognizer.minimumPressDuration = 0;
[self.view addGestureRecognizer:self.longPressGestureRecognizer];
self.swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(mySwipeAction:)];
[self.view addGestureRecognizer:self.swipeGestureRecognizer];
}
- (void)myLongPressAction:(id)sender
{
if ((self.longPressGestureRecognizer.state == UIGestureRecognizerStateEnded) || (self.longPressGestureRecognizer.state == UIGestureRecognizerStateChanged)) {
self.pressed = YES;
}
else if (self.longPressGestureRecognizer.state == UIGestureRecognizerStateBegan) {
self.pressed = NO;
[UIViewController attemptRotationToDeviceOrientation];
}
else {
self.pressed = NO;
}
}
- (void)mySwipeAction:(id)sender
{
if ((self.swipeGestureRecognizer.state == UIGestureRecognizerStateBegan) || (self.longPressGestureRecognizer.state == UIGestureRecognizerStateChanged)) {
self.pressed = YES;
}
else if (self.longPressGestureRecognizer.state == UIGestureRecognizerStateEnded) {
self.pressed = NO;
[UIViewController attemptRotationToDeviceOrientation];
}
else {
self.pressed = NO;
}
}
- (BOOL)shouldAutorotate
{
return (self.isPressed ? NO : YES);
}
#end
For detecting the touch event, I would recommend using a UIGestureRecognizer rather than overriding the UICollectionViewController's touch events. That way you can be notified of the touch event without interfering with the existing event handling.
You will probably need two custom UIGestureRecognizer classes to implement this, since I don't think UITapGestureRecognizer will meet your needs. You can have one UIGestureRecognizer notify you of finger down events and another notify you when the finger is lifted.
For preventing auto rotation, override shouldAutorotate as you have already done. When your gesture recognizer detects that the finger has been lifted, call [UIViewController attemptRotationToDeviceOrientation] to tell iOS to rotate the UI to the current orientation. (Obviously make sure that shouldAutorotate will now return YES before calling that.)

Resources