UIView animateWithDuration动画过程中UIButton无法被点击

47

我有以下代码:

[UIView animateWithDuration:0.3
                      delay:0.0
                    options:UIViewAnimationCurveEaseOut | UIViewAnimationOptionAllowUserInteraction
                 animations:^{
                     CGRect r = [btn frame];
                     r.origin.y -= 40;
                     [btn setFrame: r];
                 }
                 completion:^(BOOL done){
                     if(done){
                         [UIView animateWithDuration:0.3
                                               delay:1
                                             options:UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAllowUserInteraction
                                          animations:^{
                                              CGRect r = [btn frame];
                                              r.origin.y += 40;
                                              [btn setFrame: r];
                                          }
                                          completion:^(BOOL done){if(done) zombiePopping = 0; }];
                     }

                 }];
问题是,似乎即使我使用了UIViewAnimationOptionAllowInteraction,按钮在动画过程中也无法响应触摸事件,这对我来说有些奇怪。

也许必须使用核心动画才能使其正常工作?如果是这样的话,我该如何操作?
10个回答

62

Swift 5

如果我设置button.alpha = 0,无论是否将UIViewAnimationOptionAllowUserInteraction设置为选项,按钮的交互都会停止工作。

原因

无论是否定义动画,视图的属性都会立即应用于视图的图层。因此,当您设置view.alpha=0时,完全隐藏了view

解决方案

很简单,只需将alpha=0.1(甚至0.05)降低即可。

UIView.animate(withDuration: 2,
                       delay: 0,
                       options: [.allowUserInteraction, .overrideInheritedOptions, .curveEaseOut, .repeat, .autoreverse],
                       animations: {
                           self.button.layer.opacity = 0.01 // 0.0 will make the button unavailable to touch
        })

3
这个小技巧需要100个赞。只需1秒钟更改,一切都能正常工作。太棒了! - Xu Yin
6
最佳答案。 - Swindler
1
有史以来最好的答案。 - Zhen Liu
2
Swift 代码 UIView.animate(withDuration: 2.0, delay: 0, options:[UIViewAnimationOptions.repeat, UIViewAnimationOptions.allowUserInteraction], animations: { button.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) }, completion:nil); - Antony Ouseph
还值得一提的是,虽然我没有测试过,但如果您确实需要完全淡出,您可能可以将view.layer.opacity动画化为0.0并仍然保持触摸可用。 - Albert Renshaw
天哪!!?为什么?为什么当我有一个alpha=0的延迟动画时,即使在动画开始之前交互也不起作用?!即使我设置了UIViewAnimationOptionAllowUserInteraction选项...是哪个白痴想出这个所谓的“解决方案”的? - Mike Keskinov

53

当按钮在动画过程中时,其可触摸部分将不会与按钮的可见边框重合。

内部,按钮的框架会被设置为您的动画的最终值。你会发现你可以点击这个区域,并且它会正常工作。

要点击移动的按钮,您需要对按钮的 .layer.presentationLayer 属性进行命中测试(需要导入 QuartzCore 框架)。通常在您的视图控制器中的触摸处理方法中完成此操作。

如果您需要更多帮助,我很乐意为您扩展此答案。

以下是如何通过命中测试演示层来响应触摸事件的示例代码。此代码位于管理您的按钮的视图控制器子类中。当我这样做时,我对初始触摸比对触摸结束更感兴趣(通常情况下,单击会在触摸结束时注册),但原理是相同的。

请记得导入 QuartzCore 框架并将其添加到您的项目中。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInView:self.view];
    for (UIButton *button in self.buttonsOutletCollection)
    {
        if ([button.layer.presentationLayer hitTest:touchLocation])
        {
            // This button was hit whilst moving - do something with it here
            break;
        }
    }
}

@jrturton 谢谢,谢谢... +10.. 我在黑暗中度过了一天... 在那之前。 - TonyMkenu
有人能解释一下我应该在哪里调用这个方法吗? (NSSet *)touches 对我来说不是很清楚。我搞不明白这是什么。 - SteBra
1
@Stebra 你不需要调用这个方法。UIKit会在你的视图控制器上自动调用它。 - jrturton
以上代码中的buttonsOutletCollection是什么?请解释一下。 - Mohit
@mohitpopat 它是一个 IBOutletCollection 的出口,而 IBOutletCollection 是一个你可以在界面构建器中链接的项目数组。 - jrturton
显示剩余7条评论

34

选项的顺序很重要,您必须首先放置UIViewAnimationOptionAllowUserInteraction,然后添加其他选项。

在Swift 3中使用:options:[.allowUserInteraction]UIView的动画块内。


1
你能详细说明为什么顺序很重要吗?这是一个 OptionSet 类型,意味着它是一个位掩码,您提供每个标志的顺序根本不重要。我也看不到点赞的理由,因为没有提供任何证实您说法的来源。 - DevAndArtist
1
@DevAndArtist 你可以自己试一下,然后你就会知道为什么我说顺序很重要。如果你有不同的结果,请告诉我,或者友善一点,没有人会因回答问题而得到报酬。 - Elvis

1

Swift 3.0 在viewdidload中创建一个位于顶部的superview。

let superView = UIView(frame: view.frame)
    superView.isUserInteractionEnabled = true
    superView.backgroundColor = UIColor.clear
    superView.alpha = 0.1
    view.addSubview(superView)

现在这样实现touchesbagan:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    guard let touch = (touches as NSSet).anyObject() as? UITouch else {
        return
    }

    let touchLocation = touch.location(in: self.view)
    if btnone.layer.presentation()?.hitTest(touchLocation) != nil {
        print("1") // Do stuff for button 
    }
    if btntwo.layer.presentation()?.hitTest(touchLocation) != nil {
        print("2") // Do stuff
    }
    if btnthree.layer.presentation()?.hitTest(touchLocation) != nil {
        print(3) // Do stuff
    }
    if btnfour.layer.presentation()?.hitTest(touchLocation) != nil {
        print(4) // Do stuff
    }
}

1
如果您有一个UIButton的子类,最简单的方法是通过重写hitTest方法来实现,例如:
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        return self.layer.presentation()?.hitTest(self.convert(point, to: superview)).flatMap { _ in return self } ?? nil
    }

1
我使用NSTimer实现了这个功能: 首先,执行开始动画,然后你应该启动一个计时器,这是演示代码:
-(void)fun···
{
    [UIView animateWithDuration:0.3
                      delay:0.0
                    options:UIViewAnimationCurveEaseOut |  UIViewAnimationOptionAllowUserInteraction
                 animations:^{
                     CGRect r = [btn frame];
                     r.origin.y -= 40;
                     [btn setFrame: r];
                 }
                 completion:^(BOOL done){}];

    then , you should start an timer,example:
    //start timer
   if (timer != nil)
   {
       [timer invalidate];
       timer = nil;
   }
   [self performSelector:@selector(closeButton) withObject:nil afterDelay:0];
}


- (void)closeButton
{
    if (timer != nil)
    {
        return;
    }

    //start timer
    timer = [NSTimer scheduledTimerWithTimeInterval:1.5f target:self selector:@selector(timerEvent) userInfo:nil repeats:NO];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void)timerEvent
{
    [UIView animateWithDuration:0.3
                                               delay:1
                                             options:UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAllowUserInteraction
                                          animations:^{
                                              CGRect r = [btn frame];
                                              r.origin.y += 40;
                                              [btn setFrame: r];
                                          }
                                          completion:^(BOOL done){if(done) zombiePopping = 0; }];
    [timer invalidate];
    timer = nil;
}

1

如果您懒得写,可以使用Swift(2.0)中的相同答案。如有必要,请使用循环和输出集合进行替换:

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first
    let touchLocation = touch!.locationInView(self.view)
    if self.button.layer.presentationLayer()!.hitTest(touchLocation) != nil {
        action() // Your action, preferably the original button action.
    }
}

1

屏幕上有4个按钮,应作为向左、向右、向下和向上的导航。我使用了@nahung89答案中的简单技巧,并添加了完成操作以获得所需的完整效果。以下是代码:

  1. Connected all buttons to Outlet collection in ViewController

    @IBOutlet var navigationButtons: [UIButton]!
    
  2. Added this funcion in viewDidAppear:

        navigationButtons.forEach { button in
            UIView.animate(withDuration: 0.5,
                                    delay: 5,
                                    options: [.curveLinear, .allowUserInteraction],
                                    animations: {
                                        button.alpha = 0.1
            }) { _ in button.alpha = 0 }
        }
    

0

非常好用

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { if bounds.contains(point) && !hidden && userInteractionEnabled && enabled { return self } return super.hitTest(point, withEvent: event) }

尽管this的答案也是正确的


0

Swift版本: “999”是用于标识目标视图的标签值。

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch = touches.first
        let touchLocation = (touch?.locationInView(self.rootView))!

        for view in self.view.subviews {
            if let layer = view.layer.presentationLayer()?.hitTest(touchLocation) where view.tag == 999 {
                // Here view is the tapped view
                print(view)
            }
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接