问题
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 to YES
.
Both subviews are plain UIViews
s with a frame
and backgroundColor
.
How can I get the tap to "pass through" the UIView
and onto the UIButton
?
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
回答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.
回答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()
}
回答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.)"
Check their documentation
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
})
}
})
回答4:
I faced this issue.
In my case, i had nib loaded as subview.
UIViewController *vc = [[[NSClassFromString(@"nib_controller") alloc] initWithNibName:@"nib_name" bundle:nil] autorelease];
[self.view addSubview:vc.view];
Into the nib view (vc.view) all buttons was not being calling the action.
[(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
}
I was able to touch the buttons but the method btnActions
was not being fired.
Then, i just tried something really simple and logic.
On the parent view controller, i have an method with same name:
-(IBAction)btnActions:(id)sender{
// list star buttons for bookmark
NSLog(@"parent btnActions tag %d", [sender tag]);
// bla bla bla
}
I just added the NSLog inside to see what happens. The result appeared on log console:
parent btnActions tag xxx
This was not possible in iOS versions previous do 9.
But now, its possible to access methods from parent view if the view is loaded from nib controller view.
For who still confusing, see the bellow illustration
parent -> A
child > B
----------
| |
| A |
| |
----------
|
|--- -(IBAction)btnActions:(id)sender
----------
| |
| B |
| |
----------
|
|--- -(IBAction)btnActions:(id)sender (the controller have an method with same name as parent)
The B.view is added from A controller
----------
| |
| 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.
The buttons inside B.view send actions to -(IBAction)btnActions
Sample
[(UIButton*)[self.view viewWithTag:button_tag] addTarget:self action:@selector(btnActions:) forControlEvents:UIControlEventTouchUpInside];
All buttons seems to working well because all them could be touched or clicked (xcode simulator). But the -(IBAction)btnActions
method declared into B.controller
was not being called. I stucked on solve this for many hours until i realize the actions was being sent to the A.Controller
, as described above.
The headache here is, in the iOS early versions this was not possible.
I'm not sure, but i think from iOS9, we have this different behaviour.
The app was working well until iOS8.
回答5:
In my case, there was a .xib file for the buttons below and I added this .xib to my ViewController. @IBActions refused to work due to setting constraints for the .xib file in ViewController.
来源:https://stackoverflow.com/questions/24814401/uibutton-targets-not-working-if-a-uiview-is-added-as-a-subview