SimonBarker SimonBarker - 3 months ago 19
iOS Question

Run a loop until UIPanGestureRecognizer ends

Hopefully this isn't too vague for the mods.

I want to make a user interaction similar to the volume controls on some hifi's where you move a dial left or right to change the volume but rather than turning the dial complete revolutions you turn it left or right slightly and the more you turn it the faster the volume changes until you let go and it pings back to the middle.

In my app I want to use a UIPanGestureRecogniser where as the user pans up from the middle the volume goes up, the further from the middle the faster the increase. When they pan below the mid point of the screen the volume goes down, again faster the further from the middle you are.

The area I'm stuck is how to make this happen with out locking up the UI. I can't just use the gestureRecognizer action selector as this is only called when there is movement, for this interaction to work the user will often keep their finger in a single location while waiting for the right volume to be reached.

I feel like I want to set a loop running outside the gesturerecogniser selector and have it monitor a class variable that the gets updated when the gesture moves or ends. If it do this in the gesturerecogniser selector it will get keep running....

If this were an embedded system I would just set up some kind of interrupt based polling to check what the input control was at and keep adding to the volume until it was back to middle - can't find the comparable for iOS here.

Suggestions would be welcome, sorry mods if this is too vague - it's more of a framework methodology question that a specific code issue.

Answer

Interesting question I wrote a sample for you which must be what you want:

Objective-C code:

#import "ViewController.h"

@interface ViewController ()

@property float theValue;
@property NSTimer *timer;
@property bool needRecord;
@property UIView *dot;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    self.needRecord = NO;
    self.theValue = 0;

    UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
    circle.layer.borderWidth = 3;
    circle.layer.borderColor = [UIColor redColor].CGColor;
    circle.layer.cornerRadius = 25;
    circle.center = self.view.center;
    [self.view addSubview:circle];

    self.dot = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
    self.dot.backgroundColor = [UIColor redColor];
    self.dot.layer.cornerRadius = 20;
    self.dot.center = self.view.center;
    [self.view addSubview:self.dot];

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

    [self.dot addGestureRecognizer:pan];

    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerFire) userInfo:nil repeats:YES];
}

-(void)panHandle:(UIPanGestureRecognizer *)pan{
    CGPoint pt = [pan translationInView:self.view];
//    NSLog([NSString stringWithFormat:@"pt.y = %f",pt.y]);
    switch (pan.state) {
        case UIGestureRecognizerStateBegan:
            [self draggingStart];
            break;
        case UIGestureRecognizerStateChanged:
            self.dot.center = CGPointMake(self.view.center.x, self.view.center.y + pt.y);
            break;
        case UIGestureRecognizerStateEnded:
            [self draggingEnned];
            break;
        default:
            break;
    }
}

-(void)draggingStart{
    self.needRecord = YES;
}

-(void)draggingEnned{
    self.needRecord = NO;
    [UIView animateWithDuration:0.5 animations:^{
        self.dot.center = self.view.center;
    }];
}

-(void)timerFire{
    if (self.needRecord) {
        float distance = self.dot.center.y - self.view.center.y;
//        NSLog([NSString stringWithFormat:@"distance = %f",distance]);
        self.theValue -= distance/1000;
        NSLog([NSString stringWithFormat:@"theValue = %f",self.theValue]);
    }
}

@end

I'm learning Swift right now, so if you need, this is Swift code:

class ViewController: UIViewController {

    var lbInfo:UILabel?;
    var theValue:Float?;
    var timer:NSTimer?;
    var needRecord:Bool?;
    var circle:UIView?;
    var dot:UIView?;

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        needRecord = false;
        theValue = 0;

        lbInfo = UILabel(frame: CGRect(x: 50, y: 50, width: UIScreen.mainScreen().bounds.width-100, height: 30));
        lbInfo!.textAlignment = NSTextAlignment.Center;
        lbInfo!.text = "Look at here!";
        self.view.addSubview(lbInfo!);

        circle = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50));
        circle!.layer.borderWidth = 3;
        circle!.layer.borderColor = UIColor.redColor().CGColor;
        circle!.layer.cornerRadius = 25;
        circle!.center = self.view.center;
        self.view.addSubview(circle!);

        dot = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 40));
        dot!.backgroundColor = UIColor.redColor();
        dot!.layer.cornerRadius = 20;
        dot!.center = self.view.center;
        self.view.addSubview(dot!);

        let pan = UIPanGestureRecognizer(target: self, action: #selector(ViewController.panhandler));
        dot!.addGestureRecognizer(pan);

        timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: #selector(ViewController.timerFire), userInfo: nil, repeats: true)
    }

    func panhandler(pan: UIPanGestureRecognizer) -> Void {
        let pt = pan.translationInView(self.view);
        switch pan.state {
        case UIGestureRecognizerState.Began:
            draggingStart();
        case UIGestureRecognizerState.Changed:
            self.dot!.center = CGPoint(x: self.view.center.x, y: self.view.center.y + pt.y);
        case UIGestureRecognizerState.Ended:
            draggingEnded();
        default:
            break;
        }
    }

    func draggingStart() -> Void {
        needRecord = true;
    }

    func draggingEnded() -> Void {
        needRecord = false;
        UIView.animateWithDuration(0.1, animations: {
            self.dot!.center = self.view.center;
        });
    }

    @objc func timerFire() -> Void {
        if(needRecord!){
            let distance:Float = Float(self.dot!.center.y) - Float(self.view.center.y);
            theValue! -= distance/1000;
            self.lbInfo!.text = String(format: "%.2f", theValue!);
        }
    }
}

Hope it can help you.

If you still need some advice, just leave it here, I will check it latter.