运行一个循环,直到 UIPanGestureRecognizer 结束

Posted

技术标签:

【中文标题】运行一个循环,直到 UIPanGestureRecognizer 结束【英文标题】:Run a loop until UIPanGestureRecognizer ends 【发布时间】:2016-09-06 17:39:18 【问题描述】:

希望这对于模组来说不是太模糊。

我想让用户交互类似于某些高保真音响上的音量控制,您可以向左或向右移动拨盘来改变音量,但不是将拨盘完全转动一圈,而是稍微向左或向右转动,并且转动得越多音量变化的速度越快,直到你松开它,它就会回到中间。

在我的应用程序中,我想使用 UIPanGestureRecogniser,当用户从中间向上平移时,音量会上升,离中间越远,增加的速度越快。当他们在屏幕中点以下平移时,音量会降低,距离中间越远,音量也会降低。

我遇到的问题是如何在不锁定 UI 的情况下实现这一点。我不能只使用 gestureRecognizer 动作选择器,因为它只在有移动时调用,为了让这种交互起作用,用户通常会在等待达到正确音量时将手指保持在一个位置。

我觉得我想设置一个在手势识别器选择器之外运行的循环,并让它监视一个类变量,当手势移动或结束时该变量会更新。如果它在手势识别器选择器中执行此操作,它将继续运行......

如果这是一个嵌入式系统,我会设置某种基于中断的轮询来检查输入控件的位置并不断增加音量直到它回到中间 - 在这里找不到 ios 的可比性.

欢迎提出建议,如果这太含糊,请原谅模组 - 它更多的是特定代码问题的框架方法问题。

【问题讨论】:

您应该创建一个自定义手势识别器,而不是使用内置的。您将可以访问像 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; 这样的方法,这将允许更精细的控制 【参考方案1】:

有趣的问题我为你写了一个样本,一定是你想要的:

Objective-C 代码:

#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

我现在正在学习 Swift,所以如果你需要,这是 Swift 代码:

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

希望对你有帮助。

如果您还需要一些建议,请留在这里,我稍后会检查它。

【讨论】:

谢谢,表面上看起来它会做我想做的事——我需要把它转换成 Swift(这不是问题,我过去做过 ObjC)所以会报告明天回来。使用 NSTimer 作为中断是有意义的。 我贴一个Swift代码给你,我刚开始学习Swift,如果有什么不对的地方,请告诉我,我们一起进步。 我认为您在这里有一个好主意,但要明确一点:有两个重要的、独立的想法:(1) 管理 gr 的简单样板材料(请记住,提供 translationInView: ) 和 (2) 基于缩放的 gr 输入,通过在循环计时器提供的某个固定时间间隔内改变移动距离来改变某物的“速度”。 感谢您的建议,但根据SimmonBarker的要求“用户从中间向上平移音量增大,从中间越远增加越快。”,我觉得我们不能使用“速度”,“距离”更合适。 是的,这正是我所追求的——我一直在努力改变的范式是如何使用计时器。我主要在嵌入式历史上进行编程,所有的定时器都运行一个定时器有点浪费,我忘记了在 iphone 上每 0.1 秒触发一个定时器没什么!关于您的 swift 的一些小反馈,如果您将 iVar 声明在顶部,例如 var theValue: CGFloat = 0.0 var timer = NSTimer(),那么您不需要 !到处都是,而且我认为您不必声明 func 返回 void,只是不要在有趣的名称和括号之后包含它。【参考方案2】:

这听起来像是一个有趣的挑战。我完全不明白hifi的东西。但我所描绘的是一个圆形旋钮,9 点钟位置几乎在边缘有一个小点。当您将旋钮向右转动时,圆点向 12 点钟方向移动,音量增大,圆点距离 9 点钟方向越远,加速越快。只要点在9以上,它就会继续增加,只是增加的加速度发生变化。

左转时,圆点向 6 点方向移动,音量减小,加速度取决于距 9 点的径向距离。如果这个假设是正确的,我认为以下方法会起作用..

我会用一点三角函数来解决这个问题。要获得加速度,您需要与 9 点钟轴(负 x 轴)的角度。正角度增加体积,负角度减少体积,加速度取决于旋转度数。这个角度也会给你一个变换,你可以应用到视图来改变点的位置。我认为这不会需要任何太花哨的代码。在这种情况下,我将最大旋转设置为 90 度或 pi/2 弧度。如果您实际上可以转动更多的旋钮,则需要更改一些代码。

var volume: Double = 0
var maxVolume: Double = 100
var increasing : Bool

var multiplier: Double = 0
var cartesianTransform = CGAffineTransform()
let knobViewFrame = CGRect(x: 50, y: 50, width: 100, height: 100)
let knobRadius: CGFloat = 45
let knob = UIView()
var timer : NSTimer?

func setTransform() 

    self.cartesianTransform = CGAffineTransform(a: 1/knobRadius, b: 0, c: 0, d: -1/knobRadius, tx: knobViewFrame.width/2, ty: knobViewFrame.height * 1.5)
   // admittedly, I always have to play with these things to get them right, so there may be some errors. This transform should turn the view into a plane with (0,0) at the center, and the knob's circle at (-1,0) 


func panKnob(pan: UIPanGestureRecognizer) 

    let pointInCartesian = CGPointApplyAffineTransform(pan.locationInView(pan.view!), cartesianTransform)
    if pan.state == .Began 
        increasing = pointInCartesian.y > 0
        timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: #selector(increaseVolume), userInfo: nil, repeats: true)

    
    let arctangent = CGFloat(M_PI) - atan2(pointInCartesian.y, pointInCartesian.x)

    let maxAngle = increasing ? CGFloat(M_PI)/2 : -CGFloat(M_PI)/2

    var angle: CGFloat

    if increasing 
        angle = arctangent > maxAngle ? maxAngle : arctangent
     else 
        angle = arctangent < maxAngle ? maxAngle : arctangent
    

    knob.transform = CGAffineTransformMakeRotation(angle)

    self.multiplier = Double(angle) * 10

     if pan.state == .Ended || pan.state == .Cancelled || pan.state == .Failed 
        timer?.invalidate()
        UIView.animateWithDuration(0.75, delay: 0, options: .CurveEaseIn, animations: self.knob.transform = CGAffineTransformIdentity, completion: nil)
    




func increaseVolume() 
    let newVolume = volume + multiplier
    volume = newVolume > maxVolume ? maxVolume : (newVolume < 0 ? 0 : newVolume)
    if volume == maxVolume || volume == 0 || multiplier == 0 
        timer?.invalidate()
    

我还没有测试过上述内容,但这似乎是一个很酷的谜题。乘数仅在角度变化时发生变化,并且在定时器有效期间,音量不断增加乘数。如果您希望音量在不改变角度的情况下持续加速,请将乘数变化移动到计时器的选择器,并将角度保留为类变量,以便您知道加速它的速度。

edit:您可以在没有转换的情况下做到这一点,只需获取点和 locationInView 之间的增量。

【讨论】:

以上是关于运行一个循环,直到 UIPanGestureRecognizer 结束的主要内容,如果未能解决你的问题,请参考以下文章

运行while循环,直到收到特定输入,但如果没有收到c ++则重新运行

带有睡眠循环的PHP直到循环结束才更新数据库

如何使一行代码在循环中运行一次,并且只运行下一行代码,直到前一行运行一次?

如何延迟 For 循环直到声音在 Swift 中播放完毕

如何修复:Xcode 9 构建循环直到退出代码 255

VBA 使用“或”条件执行直到循环