0

Ok so I have a app with a left menu and a right view controller which are controlled by the UIPanGestureRecognizer. So when a user swipes the bezel (20px) of the edge of the screen it navigates.

In a settings view controller I have UISwitches which control various other things, but one of them I want to control how many touches the UIPanGestureRecognizer will take.

The switch is set up properly and sends the user defaults to the other view controller, but on the receiving end of it, it won't read what the user has selected.

UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureCallback:)];

//Switch for SwipeNav
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
BOOL switchOn = [userDefaults boolForKey:@"SwipeNav"];

if (switchOn) {
    // 2 Finger SwipeNav
    [pan setMinimumNumberOfTouches:2];
    [pan setMaximumNumberOfTouches:2];

}else{
    [pan setMinimumNumberOfTouches:1];
    [pan setMaximumNumberOfTouches:1];
}
[pan setDelegate:self];
[self.view addGestureRecognizer:pan];

How can I make it so when the switch is on, it uses 2 touches to navigate, and off it acts like normal?

Any assistance is always appreciated. Thank you.

UPDATED with settings sync code.

    if (swipe){
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setBool:self.swipe.on forKey:@"SwipeNav"];
    [userDefaults synchronize];

    if ([swipe isOn]) {
        [swipe setOn:YES animated:YES];
        [self.tableView reloadData];
        [[UIApplication sharedApplication] reloadInputViews];

    } else {
        [swipe setOn:NO animated:YES];
        [self.tableView reloadData];
        [[UIApplication sharedApplication] reloadInputViews];
    }
}
ChrisOSX
  • 724
  • 2
  • 11
  • 28
  • Please clarify what won't read on what the user has selected? userDefaults is not returning the correct bookForKey? – Yan Nov 02 '15 at 22:10
  • Correct. It's not returning any key. Even though it's the same in the settings and in the above code. I've used the same scheme for other switches that are in there, so I know the code works. I just didn't know if gestures handle user defaults differently. – ChrisOSX Nov 02 '15 at 22:17
  • Can you post the code where you setting this key. Maybe you are not synchronizing userdefaults ? Is it always returning no? From Apple Docs: If a boolean value is associated with defaultName in the user defaults, that value is returned. Otherwise, NO is returned. – Yan Nov 02 '15 at 22:17
  • OP updated to reflect the settings code. – ChrisOSX Nov 02 '15 at 22:23
  • Again to clarify in if(swipe) swipe is a local variable or it should be self.swipe? Does it actually going inside of the if statement to set the user defaults or swipe is nil? – Yan Nov 02 '15 at 22:35
  • Yes swipe is a local variable. Declared as "UISwitch *swipe;" in the .h file. I can try self.swipe, however this would confuse me as none of my other switches have that implemented. – ChrisOSX Nov 03 '15 at 03:10
  • You are sure that it's going inside the if and it's saving the data. Try to run inside the if if(self.swipe.on)NSLog(@"YES"); to make sure that it's executing setbool – Yan Nov 03 '15 at 03:16
  • UISwitch *swipe is an outlet and connected to the UISwitch in the app? I would think if it is an instance variable you would always use self.swipe to make it more concise and will let you access the property through the setters and getters – Yan Nov 03 '15 at 03:19
  • Using the NSLog(@"YES"); and NSLog(@"NO"); for the actions shows me it's functioning as it should. The swipe switch isn't a IBOutlet. I'm doing my app in all code, no storyboards, xibs, nibs. – ChrisOSX Nov 03 '15 at 03:29
  • On the receiving end when you retrieve the BOOL switchOn it's always no? – Yan Nov 03 '15 at 04:15
  • The way things are working yes, the switch isn't setting the proper number of touches, so it acts as the default 1 finger swipe. I've played around with it a little bit yesterday, and it's either 1 or 2 finger swipe, but not selectable via the switch. – ChrisOSX Nov 03 '15 at 12:24
  • Are you still working in this issue? I will try to reproduce it and see what happens if you are. – Yan Nov 04 '15 at 15:56
  • It's something I would like to fix yes. – ChrisOSX Nov 04 '15 at 16:50

2 Answers2

0

I created a new project and tried to reproduce the problem that you are seeing and everything seems to work as expected. When switch is on the you need two buttons to pan and when switch is off you only need one. Let me know if that points you in the right direction. Your code seems to be correct.

I copy pasted the handlePan function from How to use UIPanGestureRecognizer to move object? iPhone/iPad

Created everything programmatically and copy pasted your code to save the state of the switch. Here is the code from two UIViewControllers

//
//  ViewController.m
//  StackOverflowSwitch
//
//  
// 
//

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic,strong) UIButton *myButton;
@property (nonatomic,strong) UISwitch *switchOn;


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.myButton = [[UIButton alloc]initWithFrame:CGRectMake(10, 100, 150, 150)];
    self.switchOn = [[UISwitch alloc]initWithFrame:CGRectMake(10, 300, 100, 100)];
    self.myButton.backgroundColor =[UIColor grayColor];
    [self.myButton setTitle:@"Click Me" forState:UIControlStateNormal];
    [self.myButton addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.switchOn];
    [self.view addSubview:self.myButton];
    // Do any additional setup after loading the view, typically from a nib.
}

-(void)click:(UIButton *)sender
{
    [self performSegueWithIdentifier:@"NextViewController" sender:self];
}


-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{

    if(self.switchOn.on){
        NSLog(@"switch is on");
    }else{
        NSLog(@"switch is off");
    }
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setBool:self.switchOn.on forKey:@"SwipeNav"];
    [userDefaults synchronize];

}

@end

//////

#import "SecondViewController.h"
@interface SecondViewController() <UIGestureRecognizerDelegate>

@property (strong,nonatomic) UIView *myView;

@end


@implementation SecondViewController



-(void)viewDidLoad
{
    [super viewDidLoad];
    self.myView = [[UIView alloc]initWithFrame:CGRectMake(10, 10, 200, 200)];
    self.myView.backgroundColor = [UIColor redColor];
    [self.view addSubview:self.myView];

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];

    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    BOOL switchOn = [userDefaults boolForKey:@"SwipeNav"];

    if (switchOn) {
        // 2 Finger SwipeNav
        [pan setMinimumNumberOfTouches:2];
        [pan setMaximumNumberOfTouches:2];

    }else{
        [pan setMinimumNumberOfTouches:1];
        [pan setMaximumNumberOfTouches:1];
    }
    [pan setDelegate:self];
    [self.view addGestureRecognizer:pan];



    [self.myView addGestureRecognizer:pan];
}


-(void)handlePan:(UIPanGestureRecognizer *)recognizer
{
    CGPoint translation = [recognizer translationInView:self.view];
    recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
                                         recognizer.view.center.y + translation.y);
    [recognizer setTranslation:CGPointMake(0, 0) inView:self.view];

    if (recognizer.state == UIGestureRecognizerStateEnded) {

        CGPoint velocity = [recognizer velocityInView:self.view];
        CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
        CGFloat slideMult = magnitude / 200;
        NSLog(@"magnitude: %f, slideMult: %f", magnitude, slideMult);

        float slideFactor = 0.1 * slideMult; // Increase for more of a slide
        CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),
                                         recognizer.view.center.y + (velocity.y * slideFactor));
        finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);
        finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);

        [UIView animateWithDuration:slideFactor*2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
            recognizer.view.center = finalPoint;
        } completion:nil];

    }
}

@end

///// Update

I am not exactly sure how [[UIApplication sharedApplication] reloadInputViews]; calls the function to update the UIPanGestureRecongizer in your code but i tried to recreate your functionality as close as possible by having the setupView method called every time i click the UISwitch
I created a custom view with UIPanGestureReconginzer in there and a method that I call from the UIViewController to update setNumberOfTouches. The view works as expected. The key here is not to initialize a new UIPanGuestureRecongizer but update the existing one. I assume in your app UIViewController or UIView which has the UIPanGuestureRecongizer is not being fully reloaded.
I created a property UIPanGuestureRecongizer which gets initialized the first time it's being accessed and then gets updated when switch is being pressed. Here is the custom UIView code. Let me know if you have any questions regarding the code.

#import "MyTestView.h"
@interface MyTestView() <UIGestureRecognizerDelegate>


@property(nonatomic,strong) UIPanGestureRecognizer *panGesture;
@end


@implementation MyTestView



-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self setupView];
    }
    return self;
}



-(UIPanGestureRecognizer *)panGesture
{
    //lazy instantiation

    if (!_panGesture) {
        _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
    }

    return _panGesture;
}

-(void)setupView
{
    //this gets called everytime the UISwitch is being pressed.

    NSLog(@"Setup views");

    self.backgroundColor = [UIColor redColor];


    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    BOOL switchOn = [userDefaults boolForKey:@"SwipeNav"];

    if (switchOn) {
        // 2 Finger SwipeNav
        [self.panGesture setMinimumNumberOfTouches:2];
        [self.panGesture setMaximumNumberOfTouches:2];

    }else{
        [self.panGesture setMinimumNumberOfTouches:1];
        [self.panGesture setMaximumNumberOfTouches:1];
    }
    [self.panGesture setDelegate:self];
    [self addGestureRecognizer:self.panGesture];



}


-(void)handlePan:(UIPanGestureRecognizer *)recognizer
{
    CGPoint translation = [recognizer translationInView:self];
    recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
                                         recognizer.view.center.y + translation.y);
    [recognizer setTranslation:CGPointMake(0, 0) inView:self];

    if (recognizer.state == UIGestureRecognizerStateEnded) {

        CGPoint velocity = [recognizer velocityInView:self];
        CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
        CGFloat slideMult = magnitude / 200;
        NSLog(@"magnitude: %f, slideMult: %f", magnitude, slideMult);

        float slideFactor = 0.1 * slideMult; // Increase for more of a slide
        CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),
                                         recognizer.view.center.y + (velocity.y * slideFactor));
        finalPoint.x = MIN(MAX(finalPoint.x, 0), self.bounds.size.width);
        finalPoint.y = MIN(MAX(finalPoint.y, 0), self.bounds.size.height);

        [UIView animateWithDuration:slideFactor*2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
            recognizer.view.center = finalPoint;
        } completion:nil];

    }
}


@end
Community
  • 1
  • 1
Yan
  • 3,533
  • 4
  • 24
  • 45
  • I really appreciate all of the effort. And going through everything comparing stuff. i knew my code worked as it should. Just one catch, i have to restart my app for the gestures to take to what they're switched to. – ChrisOSX Nov 05 '15 at 13:20
  • After you said that it works only after you restart your app I have an idea. I think I know the reason but can't really test it now. When you are calling the method that sets or updates the uipangesturerecongnizer and you are not actually reloading the view, you are initializing a new uipangesturerecongnizer every time and adding it to the view. You are not updating the existing one. Try to make the uipangesturerecongnizer as a property of the class and initialize it only when it's not initialized(lazy init) and when it is update the number and of touches. – Yan Nov 05 '15 at 13:42
  • I figured that much, I also thought that my call I make in my settings, [self.table reloadData]; [[UIApplication sharedApplication] reloadInputViews]; would have reloaded it but maybe I will have to try what you're saying. – ChrisOSX Nov 05 '15 at 14:27
  • I am not 100% sure but because you are adding a multiple recognizers even though it's the same uipangesturerecognizer in fact you are stacking them in touch of each other and not replacing them. I don't think reloadinputviews will get rid of the uigesturerecognizer. – Yan Nov 05 '15 at 15:17
  • Ok with that said, how would I restart the uipangesturerecognizer's so they don't stack? and respond to the UISwitch? – ChrisOSX Nov 07 '15 at 22:54
  • I updated the answer where i tried to recreate your situation. It works when you are not stacking the gesture recognizer. Let me know if that helped. – Yan Nov 08 '15 at 03:59
  • Ok will try what you have. If it helps in the troubleshooting process, I am using MMDrawerController for my app base. – ChrisOSX Nov 08 '15 at 23:18
0

Alright, so after much testing/code flipping, I think I have found my solution.

So a little more backstory, I use MMDrawerController, so the method above about placing [self setupView]; in (instancetype)initWithCoder didn't do anything. The setupView or in my case is called "setupGestureRecognizers" is loaded in the viewDidLoad. Anywhere else it wouldn't load the gesture's at all.

With my current switch code and gesture loading I have this:

    -(UIPanGestureRecognizer *)panGestureNav {

    if (!_panGesture) {
        _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureCallback:)];
    }

    return _panGesture;
}

-(void)setupGestureRecognizers{

    switchOn = [[NSUserDefaults standardUserDefaults] boolForKey:@"SwipeNav"];

    if (switchOn == YES) {
        // 2 Finger SwipeNav
        [self.panGestureNav setMinimumNumberOfTouches:2];
        [self.panGestureNav setMaximumNumberOfTouches:2];
        NSLog(@"2 Finger Nav");

    }else {
        [self.panGestureNav setMinimumNumberOfTouches:1];
        [self.panGestureNav setMaximumNumberOfTouches:1];
        NSLog(@"Normal Nav");
    }
    [self.panGestureNav setDelegate:self];
    [self.view addGestureRecognizer:self.panGestureNav];

    UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureCallback:)];
    [tap setDelegate:self];
    [self.view addGestureRecognizer:tap];

}

Which would normally work, but this library does things differently, so I have to look into their gesture calling and what I ended up doing was calling it here:

-(MMOpenDrawerGestureMode)possibleOpenGestureModesForGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer withTouch:(UITouch*)touch{
    CGPoint point = [touch locationInView:self.childControllerContainerView];
    MMOpenDrawerGestureMode possibleOpenGestureModes = MMOpenDrawerGestureModeNone;
    if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]){

        [self setupGestureRecognizers]; <------------

So now it loads and functions as I want.

Thank you Yan so all of your assistance. Hope this helps somebody.

ChrisOSX
  • 724
  • 2
  • 11
  • 28