UIButton targets not working if a UIView is added as a subview
Solution 1
Set userInteractionEnabled
to NO
for your subviews to let touches pass through them and onto your button.
Also make sure to change _verticle.userInteractionEnabled = NO;
in your lazy-loader for your horizontal
property to _horizontal.userInteractionEnabled = NO;
as I believe that's a typo.
Solution 2
please use the code like below (replace the 'ViewControllerFilterCategory' with your own one + the nibName) to add the controller from nib (child one) as the child controller of current controller before adding the view as sub view. Then the iOS will call actions attached to the child controller as you expect:
let vcCats = ViewControllerFilterCategory(nibName: "ViewControllerFilterCategory", bundle: Bundle.main);
self.addChildViewController(vcCats);
self.view.addSubview(vcCats.view);
P.S. don't forget to call 'removeFromParentViewController' before removing your sub view (like below):
@IBAction func onCanceled(_ sender: Any) {
self.removeFromParentViewController()
self.view.removeFromSuperview()
}
Solution 3
You have to set isUserInteractionEnabled to true for the view you are trying to add gesture targets.
Also the user interaction is disabled when animation is on as per documentation of Apple UIView.animate function.
"During an animation, user interactions are temporarily disabled for the views being animated. (Prior to iOS 5, user interactions are disabled for the entire application.)"
So the solution is to run any animation inside DispatchQueue.main.asyncAfter
Also use this call inside completion block of UIView.animate if you are starting another animation from there
For example
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
})
}
})
Jason Silberman
Updated on July 09, 2022Comments
-
Jason Silberman almost 2 years
I have a
UIButton
. It has two subviews. I then call:[createButton addTarget:self action:@selector(openComposer) forControlEvents:UIControlEventTouchUpInside];
If I tap the parts of the
UIButton
that aren't covered but one of its subviews, it works fine. However if I tap one of the subviews instead, it does not trigger the action.On both subviews, I has
.userInteractionEnabled
set toYES
.Both subviews are plain
UIViews
s with aframe
andbackgroundColor
.How can I get the tap to "pass through" the
UIView
and onto theUIButton
?Thanks
EDIT: I need to use UIControlEvents because I need UIControlEventTouchDown.
EDIT 2:
Here is the code for the button.
@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