【问题标题】:UIButton targets not working if a UIView is added as a subview如果将 UIView 添加为子视图,则 UIButton 目标不起作用
【发布时间】:2014-09-08 23:10:53
【问题描述】:

我有一个UIButton。它有两个子视图。然后我打电话:

[createButton addTarget:self action:@selector(openComposer) forControlEvents:UIControlEventTouchUpInside];

如果我点击UIButton 中未被覆盖但它的子视图之一的部分,它工作正常。但是,如果我点击其中一个子视图,它不会触发操作。

在两个子视图上,我将.userInteractionEnabled 设置为YES

两个子视图都是普通的UIViewss,带有framebackgroundColor

如何让水龙头“通过”UIView 并进入UIButton

谢谢

编辑:我需要使用 UIControlEvents,因为我需要 UIControlEventTouchDown。

编辑 2:

这是按钮的代码。

@interface CreateButton ()

@property (nonatomic) Theme *theme;
@property (nonatomic) UIView *verticle;
@property (nonatomic) UIView *horizontal;
@property (nonatomic) BOOL mini;

@end

@implementation CreateButton

#pragma mark - Accessors

- (UIView *)verticle {
    if (! _verticle) {
        _verticle = [[UIView alloc] init];
        _verticle.backgroundColor = [self.theme colorForKey:@"createButtonPlusBackgroundColor"];
        _verticle.userInteractionEnabled = NO;
    }
    return _verticle;
}

- (UIView *)horizontal {
    if (! _horizontal) {
        _horizontal = [[UIView alloc] init];
        _horizontal.backgroundColor = [self.theme colorForKey:@"createButtonPlusBackgroundColor"];
        _verticle.userInteractionEnabled = NO;
    }
    return _horizontal;
}

#pragma mark - Init

- (instancetype)initWithTheme:(Theme *)theme frame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        _theme = theme;

        self.layer.cornerRadius = roundf(frame.size.width / 2);
        self.layer.shadowColor = [UIColor blackColor].CGColor;
        self.layer.shadowOpacity = .1f;
        self.layer.shadowOffset = CGSizeZero;
        self.layer.shadowRadius = 15.f;
        self.backgroundColor = [self.theme colorForKey:@"createButtonBackgroundColor"];

        [self addSubview:self.verticle];
        [self addSubview:self.horizontal];

        [self addTarget:self action:@selector(animate) forControlEvents:UIControlEventTouchDown];
        [self addTarget:self action:@selector(animate) forControlEvents:UIControlEventTouchUpInside];
        [self addTarget:self action:@selector(animate) forControlEvents:UIControlEventTouchUpOutside];
    }
    return self;
}

#pragma mark - Actions

- (void)animate {
    [UIView animateWithDuration:0.2 delay:0 usingSpringWithDamping:0.4 initialSpringVelocity:8 options:kNilOptions animations:^{
        if (self.mini) {
            self.transform = CGAffineTransformMakeScale(1.0, 1.0);
            self.mini = NO;
        } else {
            self.transform = CGAffineTransformMakeScale(0.90, 0.90);
            self.mini = YES;
        }
    } completion:nil];
}

#pragma mark - UIView

- (void)layoutSubviews {
    CGSize size = self.bounds.size;

    NSInteger width = 3;
    NSInteger verticleInset = 12;
    self.verticle.frame = CGRectMake((size.width - width) / 2, verticleInset, width, size.height - (verticleInset * 2));
    self.horizontal.frame = CGRectMake(verticleInset, (size.height - width) / 2, size.width - (verticleInset * 2), width);
}

@end

【问题讨论】:

  • 那是因为您没有向子视图添加触摸事件。有那么难吗?
  • 我不能使用 UIGestureRecognizer 因为我需要触发 ControlEventTouchDown 事件。
  • 子视图是在按钮框架内部还是外部?因为如果它们在外面,它们会正常显示,但在按钮的活动区域之外。
  • 并且没有处理这些子视图上的任何触摸事件?
  • 目前没有。 @galrito

标签: ios objective-c uiview uibutton


【解决方案1】:

在我的例子中,下面的按钮有一个 .xib 文件,我将这个 .xib 添加到我的 ViewController 中。由于在 ViewController 中为 .xib 文件设置了约束,@IBActions 拒绝工作。

【讨论】:

    【解决方案2】:

    对于要添加手势目标的视图,您必须将 isUserInteractionEnabled 设置为 true。

    根据 Apple UIView.animate 函数的文档,当动画打开时,用户交互也会被禁用。

    “在动画过程中,正在动画的视图会暂时禁用用户交互。(在 iOS 5 之前,整个应用程序都禁用用户交互。)”

    Check their documentation

    所以解决方案是在 DispatchQueue.main.asyncAfter 中运行任何动画

    如果你从那里开始另一个动画,也可以在 UIView.animate 的完成块中使用这个调用

    例如

    UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.7, options: [.curveEaseInOut, .allowUserInteraction], animations: {
                    topConstraint.constant = 0
                    windowView.layoutIfNeeded()
                    oldNotificationView?.removeFromSuperview()
                }, completion: { result in
                    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                        //Its important to run below animation in asyncAfter as otherwise setting delay for 4 seconds seems to change frame such that tap gesture or touchesBegan is not sent for the view. So execting after delay using dispatch queue is right way for touch events to be fired instead of using animate functions delay.
                        UIView.animate(withDuration: 0.5, delay:0
                            , options: [.curveEaseOut , .allowUserInteraction], animations: {
                            topConstraint.constant = -height
                            windowView.layoutIfNeeded()
                        } , completion: { result in
                            notificationView.removeFromSuperview()
                            self.inAppNotificationView = nil
                        })
                    }
                })
    UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.7, options: [.curveEaseInOut, .allowUserInteraction], animations: {
                topConstraint.constant = 0
                windowView.layoutIfNeeded()
                oldNotificationView?.removeFromSuperview()
            }, completion: { result in
                DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                    //Its important to run below animation in asyncAfter as otherwise setting delay for 4 seconds seems to change frame such that tap gesture or touchesBegan is not sent for the view. So execting after delay using dispatch queue is right way for touch events to be fired instead of using animate functions delay.
                    UIView.animate(withDuration: 0.5, delay:0
                        , options: [.curveEaseOut , .allowUserInteraction], animations: {
                        topConstraint.constant = -height
                        windowView.layoutIfNeeded()
                    } , completion: { result in
                        notificationView.removeFromSuperview()
                        self.inAppNotificationView = nil
                    })
                }
            })
    

    【讨论】:

      【解决方案3】:

      在将视图添加为子视图之前,请使用如下代码(将“ViewControllerFilterCategory”替换为您自己的一个 + nibName)从 nib(子一个)添加控制器作为当前控制器的子控制器。然后,iOS 将按照您的预期调用附加到子控制器的操作:

          let vcCats = ViewControllerFilterCategory(nibName: "ViewControllerFilterCategory", bundle: Bundle.main);
          self.addChildViewController(vcCats);
          self.view.addSubview(vcCats.view);
      

      附:不要忘记在删除子视图之前调用“removeFromParentViewController”(如下所示):

      @IBAction func onCanceled(_ sender: Any) {
          self.removeFromParentViewController()
          self.view.removeFromSuperview()
      }
      

      【讨论】:

      • 是的,需要加self.addChildViewController(vcCats);
      【解决方案4】:

      我遇到了这个问题。

      就我而言,我已将 nib 加载为子视图。

      UIViewController *vc = [[[NSClassFromString(@"nib_controller") alloc] initWithNibName:@"nib_name" bundle:nil] autorelease];
      [self.view addSubview:vc.view];
      

      在 nib 视图 (vc.view) 中,所有按钮都没有调用该操作。

      [(UIButton*)[self.view viewWithTag:button_tag] addTarget:self action:@selector(btnActions:) forControlEvents:UIControlEventTouchUpInside];
      
      
      -(IBAction)btnActions:(id)sender{
      
          // list star buttons for bookmark
          NSLog(@"btnActions tag %d", [sender tag]);
      
          // bla bla bla
      }
      

      我可以触摸按钮,但方法 btnActions 没有被触发。

      然后,我只是尝试了一些非常简单和逻辑的东西。

      在父视图控制器上,我有一个同名的方法:

      -(IBAction)btnActions:(id)sender{
      
          // list star buttons for bookmark
          NSLog(@"parent btnActions tag %d", [sender tag]);
      
          // bla bla bla
      }
      

      我刚刚在里面添加了 NSLog 看看会发生什么。结果出现在日志控制台上:

      parent btnActions tag xxx
      

      这在 9 之前的 iOS 版本中是不可能的。

      但是现在,如果从 nib 控制器视图加载视图,则可以从父视图访问方法。

      还有困惑的看下图

      父级 -> A
      孩子 > B

      ----------
      |        | 
      |   A    |
      |        |
      ----------
          |
          |--- -(IBAction)btnActions:(id)sender
      
      
      ----------
      |        | 
      |   B    |
      |        |
      ----------
          |
          |--- -(IBAction)btnActions:(id)sender (the controller have an method with same name as parent)
      

      B.view 是从 A 控制器添加的

      ----------
      |        | 
      |   A    |
      |        |
      ----------
          |
          |--- (touching some button -> execute this:
      
      UIViewController *B = [[[NSClassFromString(@"B_controller") alloc] initWithNibName:@"B_name" bundle:nil] autorelease];
      [self.view addSubview:B.view]; // The B.view is added as subview.
      

      B.view 中的按钮将动作发送到-(IBAction)btnActions

      样本

      [(UIButton*)[self.view viewWithTag:button_tag] addTarget:self action:@selector(btnActions:) forControlEvents:UIControlEventTouchUpInside];
      

      所有按钮似乎都运行良好,因为它们都可以被触摸或点击(xcode 模拟器)。但是声明为B.controller-(IBAction)btnActions 方法没有被调用。我坚持解决这个问题好几个小时,直到我意识到操作被发送到A.Controller,如上所述。

      令人头疼的是,在 iOS 早期版本中这是不可能的。

      我不确定,但我认为从 iOS9 开始,我们有这种不同的行为。

      该应用在 iOS8 之前运行良好。

      【讨论】:

        【解决方案5】:

        userInteractionEnabled 设置为NO 以让您的子视图让触摸通过它们并到达您的按钮。

        还要确保将您的 horizontal 属性的惰性加载器中的 _verticle.userInteractionEnabled = NO; 更改为 _horizontal.userInteractionEnabled = NO;,因为我认为这是一个错字。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-04-18
          • 1970-01-01
          • 1970-01-01
          • 2019-11-13
          • 1970-01-01
          • 2012-07-20
          相关资源
          最近更新 更多