SWRevealViewController rightViewController disable the opening - ios

I created the side menu structure using the SWReveal view controller.What I want to do is to cancel the opening of the right-side view controller on some pages.I have researched and found something like this:
- (BOOL)revealControllerPanGestureShouldBegin:(SWRevealViewController *)revealController
if([revealController.frontViewController isKindOfClass:[UINavigationController class]]){
UINavigationController *navController = (UINavigationController *)revealController.frontViewController;
UIViewController *lastViewController = navController.viewControllers.lastObject;
if([lastViewController isKindOfClass:[DetailViewController class]] ||
[lastViewController isKindOfClass:[TableDateViewController class]] ||
[lastViewController isKindOfClass:[MapViewController class]])
{
return NO; // I do not want to open it for the view controllers I want
}
}
return YES;
}
This worked for me,but it also affected the opening of the left page.There is no problem with the touch action(tap gesture),but this applies to the pan gesture.I mean the pan gesture does not work for the view controller I want to run.I want to work correctly for some view controller,but I do not want to affect the left side.
I added the right toggle like this:
-(void)sideRightMenuLoad{
[((PersonelViewController *)[self.navigationController.viewControllers objectAtIndex:0]).view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
SWRevealViewController *revealViewController = self.revealViewController;
if(revealViewController){
[self.sideRightBarButton setTarget:self.revealViewController];
[self.sideRightBarButton setAction:#selector(rightRevealToggle:)];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
This code needs to work to open the right-side page:
[self performSegueWithIdentifier:SWSegueRightIdentifier sender:nil];
I tried to run it when I wanted it, but it did not work.
I am waiting for help in this regard.Thank you.

You have to check the pan gesture's direction:
- (BOOL)revealControllerPanGestureShouldBegin:(SWRevealViewController *)revealController {
if ([revealController.panGestureRecognizer velocityInView:revealController.view].x < 0) {
// pan direction left, should open right side
// ...
return NO;
}
return YES;
}

SWIFT 4
func revealControllerPanGestureShouldBegin(_ revealController: SWRevealViewController!) -> Bool {
let point = revealController.panGestureRecognizer().location(in: self.view)
if revealController.frontViewPosition == FrontViewPosition.left && point.x < 50.0 {
print("YES YES YES YES RRRRIIIIGGGGHHHHTTTT")
return false
}
else if revealController.frontViewPosition == FrontViewPosition.right {
print("YES YES YES YES LLLLEEEEFFFFTTTT")
return true
}
return false
}

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!!

Add Gesture Recognizer to specific UITabBarController tabBarItem

I'm trying to add a gesture recognizer to specific tabBar item's subview. I can successfully add one to the tabBar itself, but not a specific index.
I was able to trick it into reacting based on a selectedIndex by implementing this in my AppDelegate:
[self.tabBar.view addGestureRecognizer:longPressNotificationsGR];
-(void)showFilterForNotifications:(UILongPressGestureRecognizer *)gesture {
if (self.tabBar.selectedIndex == 4) {
if (gesture.state == UIGestureRecognizerStateBegan) {
NSLog(#"Began");
} else if (gesture.state == UIGestureRecognizerStateEnded) {
NSLog(#"Ended");
}
}
}
Is there a better way? And yes I know this isn't what Apple had in mind but this is what I need for my particular project. I just feel like this isn't the best workaround, if there even is one, and i'm not sure how this will behave performance wide since its in the AppDelegate. But ultimately, I don't want to do it this way, I want to add it to the objectAtIndex:n so users can't just press and hold anywhere on the tabBar, even if 4 is the currently selected index. Right now users can tap & hold on index 1 icon, if 4 is selected, and the gesture methods get called. I want it to happen only if user is tapping & holding on objectAtIndex:n
You can get the UIView of a tab at a specific index with the following category method on UITabBar:
- (UIView*)tabAtIndex:(NSUInteger)index
{
BOOL validIndex = index < self.items.count;
NSAssert(validIndex, #"Tab index out of range");
if (!validIndex) {
return nil;
}
NSMutableArray* tabBarItems = [NSMutableArray arrayWithCapacity:[self.items count]];
for (UIView* view in self.subviews) {
if ([view isKindOfClass:NSClassFromString(#"UITabBarButton")] && [view respondsToSelector:#selector(frame)]) {
// check for the selector -frame to prevent crashes in the very unlikely case that in the future
// objects that don't implement -frame can be subViews of an UIView
[tabBarItems addObject:view];
}
}
if ([tabBarItems count] == 0) {
// no tabBarItems means either no UITabBarButtons were in the subView, or none responded to -frame
// return CGRectZero to indicate that we couldn't figure out the frame
return nil;
}
// sort by origin.x of the frame because the items are not necessarily in the correct order
[tabBarItems sortUsingComparator:^NSComparisonResult(UIView* view1, UIView* view2) {
if (view1.frame.origin.x < view2.frame.origin.x) {
return NSOrderedAscending;
}
if (view1.frame.origin.x > view2.frame.origin.x) {
return NSOrderedDescending;
}
NSLog(#"%# and %# share the same origin.x. This should never happen and indicates a substantial change in the framework that renders this method useless.", view1, view2);
return NSOrderedSame;
}];
if (index < [tabBarItems count]) {
// viewController is in a regular tab
return tabBarItems[index];
}
else {
// our target viewController is inside the "more" tab
return [tabBarItems lastObject];
}
return nil;
}
Then, you just need to add your gesture recognizer:
[[self.tabBarController.tabBar tabAtIndex:4] addGestureRecognizer:myGestureRecognizer];

disable rotation in single View iOS

I am trying to disable rotation for just a single viewController in iOS, i have see some questions asked for auto rotate but none for this.
I want to lock the orientation that View B opens into For ex: opens in landscape, it can only be landscape, or opens in portrait, it can only be portrait. While still allowing view A to be whatever orientation it wants.
EDIT
\n
This is how i call view B
[self.mediaFocusController showImageFromURL:url fromView:self.view withThumb:thumbUrl];
This is how it enters:
if (self.targetViewController) {
[self willMoveToParentViewController:self.targetViewController];
if ([UIView instancesRespondToSelector:#selector(setTintAdjustmentMode:)]) {
self.targetViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
[self.targetViewController.view tintColorDidChange];
}
[self.targetViewController addChildViewController:self];
[self.targetViewController.view addSubview:self.view];
if (self.snapshotView) {
[self.targetViewController.view insertSubview:self.snapshotView belowSubview:self.view];
[self.targetViewController.view insertSubview:self.blurredSnapshotView aboveSubview:self.snapshotView];
}
}
else {
// add this view to the main window if no targetViewController was set
if ([UIView instancesRespondToSelector:#selector(setTintAdjustmentMode:)]) {
self.keyWindow.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
[self.keyWindow tintColorDidChange];
}
[self.keyWindow addSubview:self.view];
if (self.snapshotView) {
[self.keyWindow insertSubview:self.snapshotView belowSubview:self.view];
[self.keyWindow insertSubview:self.blurredSnapshotView aboveSubview:self.snapshotView];
}
}
I have NavigationController set up but this view is different
Trying this code (I found that in https://stackoverflow.com/a/17377221/2040992):
- (BOOL)shouldAutorotate
{
id currentViewController = self.topViewController;
if ([currentViewController isKindOfClass:[DetailViewController class]])
return NO;
return YES;
}

iOS navigation issue: after pushing a ViewController, it results in a navigation bar showing the navigation items of the previous ViewController

This situation happens very rarely and I don't know how to reproduce it, but it does happen sometimes and I would like to fix it.
In ViewController A, if I push ViewController Bin, sometimes(not always) when ViewController B did appear, the navigation bar is showing the navigation bar items of ViewController A, not those of ViewController B. If I click on the back button, I cannot go back to ViewController A, getting stuck in ViewController B.
A UIBarButtonItem is added to the navigation items of ViewController A, and the navigation items of ViewController A will be updated in response to some events. Is it the reason that causes this issue?
Codes for pushing ViewController B
ViewControllerB* viewControllerB = [ViewControllerB new];
[self.navigationController pushViewController:viewControllerB animated:YES];
Codes for updating the navigation items in ViewController A
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if(NumberOfCalendarOperations == 0){
[self showNavigatorBarButtons];
}
else{
[self startRefreshAnimationOnUpdateButton];
}
}
//This method is triggered through notification when the number of operations in calendarOperationQueue is changed
-(void)calendarOperationQueueStateDidChange:(NSNotification*)notification{
if(NumberOfCalendarOperations == 0){
if (self.isShowActivityIndicator) {
dispatch_async(dispatch_get_main_queue(), ^{
[self showNavigatorBarButtons];
});
}
}
else{
if (!self.isShowActivityIndicator) {
dispatch_async(dispatch_get_main_queue(), ^{
[self startRefreshAnimationOnUpdateButton];
});
}
}
}
/**
* Show the update button on the right of navigation bar
*/
-(void)showNavigatorBarButtons{
self.isShowActivityIndicator = NO;
UIBarButtonItem *updateButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"sync.png"] style:UIBarButtonItemStylePlain target:self action:#selector(updateButtonDidPress:)];
[self.navigationItem setRightBarButtonItems:[NSArray arrayWithObjects:updateButton, nil]];}
/**
* Start refresh animation on the update button
*/
-(void)startRefreshAnimationOnUpdateButton{
if (self.navigationController.topViewController != self) {
return;
}
self.isShowActivityIndicator = YES;
UIView* updateButtonView = nil;
for (UIView *subViewInNavigationBar in self.navigationController.navigationBar.subviews){
NSString *subViewClassAsString = NSStringFromClass([subViewInNavigationBar class]);
if ([subViewClassAsString compare:#"UINavigationButton" /* TODO: be careful with this */
options:NSCaseInsensitiveSearch] == NSOrderedSame){
if ([subViewInNavigationBar isKindOfClass:[UIView class]] == YES){
if(updateButtonView == nil){
updateButtonView = subViewInNavigationBar;
}
else if(subViewInNavigationBar.center.x < updateButtonView.center.x){
updateButtonView = subViewInNavigationBar;
}
}
}
}
for (UIView *subViewsInButton in updateButtonView.subviews){
if ([subViewsInButton isKindOfClass:[UIImageView class]] == YES &&
subViewsInButton.frame.origin.x != 0.0f &&
subViewsInButton.frame.origin.y != 0.0f){
[subViewsInButton removeFromSuperview];
CGRect activityIndicatorFrame = self.updateButtonActivityIndicator.frame;
activityIndicatorFrame.origin.x = (CGRectGetWidth(updateButtonView.frame) / 2.0f) - (CGRectGetWidth(activityIndicatorFrame) / 2.0f);
activityIndicatorFrame.origin.y = (CGRectGetHeight(updateButtonView.frame) / 2.0f) - (CGRectGetHeight(activityIndicatorFrame) / 2.0f);
self.updateButtonActivityIndicator.frame = activityIndicatorFrame;
[self.updateButtonActivityIndicator startAnimating];
[updateButtonView addSubview:self.updateButtonActivityIndicator];
return;
}
}
}
Anyone has got a clue? Thank you.
Finally I found the reason of this issue. It is that I try to hide the navigation bar in viewWillDisappear. Now iOS allows me to back to the previous view by panning from the left edge of the screen. But while panning from the left edge, if I cancel this action, and pan back to the left edge, the navigation bar will enter this strange state.

textfieldshouldclear: not invoked by UITextfield

I am trying to invoke -(BOOL) textFieldShouldClear:(UITextField *)textField when UITextField's clear button is tapped. I have already set my delegate and UITextField's other delegate's methods are being called correctly, except this one. Clear Button is set to "is always visible" in nib file.
EDIT
FYI I am showing FPPopover when textfield's text is changed. if I tap on clear button without showing popover, clear button works fine. But if I try to tap it when popover is being displayed, delegate method is not called.
Code Snippet
-(BOOL) textFieldShouldClear:(UITextField *)textField
{
return YES;
}
- (IBAction)didChangeScripText:(id)sender {
NSString *text = isPortrait ? symbolTextField.text : landsymbolTextfield.text;
if(scripList.count == 0)
{
if([Logs sharedManager].scripData.count > 0)
[self extractScrips];
else
return;
}
// SAFE_ARC_RELEASE(popover);
// popover=nil;
//the controller we want to present as a popover
if(controller == nil)
controller = [[scripComboViewController alloc] initWithStyle:UITableViewStylePlain];
if(controller.scripListFiltered.count > 0)
[controller.scripListFiltered removeAllObjects];
controller.delegate = self;
if(popover == nil){
popover = [[FPPopoverController alloc] initWithViewController:controller];
popover.tint = FPPopoverDefaultTint;
}
controller.scripListFiltered = [NSMutableArray arrayWithArray:[scripList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF beginswith[c] %#",text]]];
NSLog(#"array is: %#",controller.scripListFiltered);
if(controller.scripListFiltered.count == 0)
{
[popover dismissPopoverAnimated:YES];
return;
}
//decide contentsize and arrow dir based on tableview height
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
popover.contentSize = CGSizeMake(300, 500);
}
else {
popover.contentSize = CGSizeMake(200, 200);
}
//sender is the uitextfield
float height = isPortrait ? portTable.frame.size.height : landTable.frame.size.height;
if(height > 0)
popover.arrowDirection = FPPopoverArrowDirectionDown;
else
popover.arrowDirection = FPPopoverArrowDirectionUp;
if(![popover isModalInPopover])
[popover presentPopoverFromView:sender];
[controller reloadTable];
}
What is going wrong? Can anyone tell me. Thanks.
Actually problem is due to FPPopover. When it receives touch event outside its view, it dismisses itself, and no interaction with outside controls is possible at that time. So if tap clear button, it will be used to dismiss the pop up and then I am able to use clear button. Thats all.

Resources