[英]UIButton in cell in collection view not receiving touch up inside event
以下代码表达了我的问题:(它是独立的,因为您可以使用空模板创建一个 Xcode 项目,替换 main.m 文件的内容,删除 AppDelegate.h/.m 文件并构建它)
//
// main.m
// CollectionViewProblem
//
#import <UIKit/UIKit.h>
@interface Cell : UICollectionViewCell
@property (nonatomic, strong) UIButton *button;
@property (nonatomic, strong) UILabel *label;
@end
@implementation Cell
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
self.label = [[UILabel alloc] initWithFrame:self.bounds];
self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.label.backgroundColor = [UIColor greenColor];
self.label.textAlignment = NSTextAlignmentCenter;
self.button = [UIButton buttonWithType:UIButtonTypeInfoLight];
self.button.frame = CGRectMake(-frame.size.width/4, -frame.size.width/4, frame.size.width/2, frame.size.width/2);
self.button.backgroundColor = [UIColor redColor];
[self.button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:self.label];
[self.contentView addSubview:self.button];
}
return self;
}
// Overriding this because the button's rect is partially outside the parent-view's bounds:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if ([super pointInside:point withEvent:event])
{
NSLog(@"inside cell");
return YES;
}
if ([self.button
pointInside:[self convertPoint:point
toView:self.button] withEvent:nil])
{
NSLog(@"inside button");
return YES;
}
return NO;
}
- (void)buttonClicked:(UIButton *)sender
{
NSLog(@"button clicked!");
}
@end
@interface ViewController : UICollectionViewController
@end
@implementation ViewController
// (1a) viewdidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.collectionView registerClass:[Cell class] forCellWithReuseIdentifier:@"ID"];
}
// collection view data source methods ////////////////////////////////////
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 100;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
Cell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"ID" forIndexPath:indexPath];
cell.label.text = [NSString stringWithFormat:@"%d", indexPath.row];
return cell;
}
///////////////////////////////////////////////////////////////////////////
// collection view delegate methods ////////////////////////////////////////
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"cell #%d was selected", indexPath.row);
}
////////////////////////////////////////////////////////////////////////////
@end
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ViewController *vc = [[ViewController alloc] initWithCollectionViewLayout:layout];
layout.itemSize = CGSizeMake(128, 128);
layout.minimumInteritemSpacing = 64;
layout.minimumLineSpacing = 64;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.sectionInset = UIEdgeInsetsMake(32, 32, 32, 32);
self.window.rootViewController = vc;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
@end
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
基本上,我正在使用集合视图创建一个 Springboard 类型的 UI。 我的UICollectionViewCell子类 ( Cell ) 有一个按钮,它部分位于单元格的contentView (即它的超级视图)边界之外。
问题是单击contentView边界之外的按钮的任何部分(基本上是按钮的 3/4)不会调用按钮操作。 仅当单击与contentView重叠的按钮部分时,才会调用按钮的操作方法。
我什至覆盖了Cell中的-pointInside:withEvent:
方法,以便确认按钮中的触摸。 但这对解决按钮点击问题没有帮助。
我猜这可能与collectionView处理触摸的方式有关,但我不知道是什么。 我知道UICollectionView是一个UIScrollView子类,我实际上已经测试了覆盖-pointInside:withEvent:
在包含部分重叠按钮的视图(使子视图成为滚动视图)上解决了按钮点击问题,但它在这里不起作用.
有什么帮助吗?
** 添加:为了记录,我当前的问题解决方案涉及将较小的子视图插入到contentView中,从而为单元格提供外观。 删除按钮被添加到contentView ,这样它的 rect 实际上位于 contentView 的边界内,但仅部分重叠单元格的可见部分(即插入子视图)。 所以我得到了我想要的效果,并且按钮工作正常。 但是我仍然对上面原始实现的问题感到好奇。
问题似乎是hitTest / pointInside。 我猜测单元格是从pointInside返回NO,如果触摸位于单元格外部的按钮部分,因此按钮不会受到测试。 要解决此问题,您必须覆盖UICollectionViewCell子类上的pointInside以将该按钮考虑在内。 如果触摸在按钮内,您还需要覆盖hitTest以返回按钮。 下面是示例实现,假设您的按钮位于名为deleteButton的UICollectionViewCell子类中的属性中。
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [self.deleteButton hitTest:[self.deleteButton convertPoint:point fromView:self] withEvent:event];
if (view == nil) {
view = [super hitTest:point withEvent:event];
}
return view;
}
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if ([super pointInside:point withEvent:event]) {
return YES;
}
//Check to see if it is within the delete button
return !self.deleteButton.hidden && [self.deleteButton pointInside:[self.deleteButton convertPoint:point fromView:self] withEvent:event];
}
请注意,因为hitTest和pointInside期望该点位于接收器的坐标空间中,所以您必须记住在按钮上调用这些方法之前转换该点。
在Interface Builder中,您是否将对象设置为UICollectionViewCell? 因为错误的一次我设置了一个UIView并在为它分配了正确的UICollectionViewCell类之后......但是这些东西(按钮,标签,ecc。)没有被添加到contentView中,因此它们不会像它们那样响应......
所以,在IB中提醒在绘制界面时采用UICollectionViewCell对象:)
Swift版本:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
//From higher z- order to lower except base view;
for (var i = subviews.count-2; i >= 0 ; i--){
let newPoint = subviews[i].convertPoint(point, fromView: self)
let view = subviews[i].hitTest(newPoint, withEvent: event)
if view != nil{
return view
}
}
return super.hitTest(point, withEvent: event)
}
这就是...对于所有子视图
我成功接收到子类UICollectionViewCell.m文件中按如下方式创建的按钮的触摸;
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
// Create button
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, 100, 100); // position in the parent view and set the size of the button
[button setTitle:@"Title" forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:@"animage.png"] forState:UIControlStateNormal];
[button addTarget:self action:@selector(button:) forControlEvents:UIControlEventTouchUpInside];
// add to contentView
[self.contentView addSubview:button];
}
return self;
}
我意识到在Storyboard中添加的按钮不起作用,我在代码中添加了按钮,不确定这是否在最新的Xcode中修复。
希望有所帮助。
根据要求接受的答案,我们应该进行一次hitTest
以接收细胞内的触摸。 这是命中测试的Swift 4代码:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for i in (0..<subviews.count-1).reversed() {
let newPoint = subviews[i].convert(point, from: self)
if let view = subviews[i].hitTest(newPoint, with: event) {
return view
}
}
return super.hitTest(point, with: event)
}
我有一个类似的问题,试图在一个uicollectionview单元格的边界之外放置一个删除按钮,它没有接缝来响应点击事件。
我解决它的方法是在集合上放置一个UITapGestureRecognizer,当点击发生时,预先形成以下代码
//this works also on taps outside the cell bouns, im guessing by getting the closest cell to the point of click.
NSIndexPath* tappedCellPath = [self.collectionView indexPathForItemAtPoint:[tapRecognizer locationInView:self.collectionView]];
if(tappedCellPath) {
UICollectionViewCell *tappedCell = [self.collectionView cellForItemAtIndexPath:tappedCellPath];
CGPoint tapInCellPoint = [tapRecognizer locationInView:tappedCell];
//if the tap was outside of the cell bounds then its in negative values and it means the delete button was tapped
if (tapInCellPoint.x < 0) [self deleteCell:tappedCell];
}
我看到两个原始答案的快速转换并不是完全快速的转换。 所以我只想给原始答案的Swift 4
转换,以便每个想要使用它的人。 您只需将代码粘贴到subclassed
UICollectionViewCell
。 只需确保使用您自己的按钮更改closeButton
。
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var view = closeButton.hitTest(closeButton.convert(point, from: self), with: event)
if view == nil {
view = super.hitTest(point, with: event)
}
return view
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if super.point(inside: point, with: event) {
return true
}
return !closeButton.isHidden && closeButton.point(inside: closeButton.convert(point, from: self), with: event)
}
在我看来,Honus有最好的答案。 事实上,唯一一个适合我的人,所以我一直在回答其他类似的问题并以这种方式发送:
我花了几个小时在网上搜索我的UIButton内部UICollectionView的解决方案不起作用。 让我疯了,直到我终于找到适合我的解决方案。 我相信这也是正确的方法:黑客攻击测试。 这个解决方案可以比修复UICollectionView Button问题更深入(双关语),因为它可以帮助您将click事件发送到隐藏在阻止事件通过的其他视图下的任何按钮:
集合视图中的单元格中的UIButton未在内部事件中接收修饰
由于SO的答案是在Objective C中,我从那里听到了线索,找到了一个快捷的解决方案:
http://khanlou.com/2018/09/hacking-hit-tests/
-
当我在单元格上禁用用户交互,或者我试过的任何其他各种答案时,没有任何效果。
我上面发布的解决方案的优点在于,您可以将addTarget和selector功能留给您如何使用它们,因为它们从来都不是问题。 您只需覆盖一个功能即可帮助触摸事件将其发送到目的地。
解决方案的工作原理:
在最初的几个小时里,我发现我的addTarget调用没有正确注册手势。 事实证明目标是正常的。 触摸事件根本就没有到达我的按钮。
现实似乎来自我读过的任何数量的SO帖子和文章,UICollectionView Cells旨在容纳一个动作,而不是多个因各种原因。 所以你应该只使用内置的选择动作。 考虑到这一点,我认为绕过此限制的正确方法不是破解UICollectionView来禁用滚动或用户交互的某些方面。 UICollectionView只是在做它的工作。 正确的方法是破解命中测试,以便在它到达UICollectionView之前拦截它,并找出他们正在点击的项目。 然后,您只需将触摸事件发送到他们正在点击的按钮,然后让您的正常工作完成工作。
我的最终解决方案(来自khanlou.com文章)是将我的addTarget声明和我的选择器函数放在我喜欢的任何地方(在单元格类或cellForItemAt覆盖中),以及在覆盖hitTest函数的单元格类中。
在我的细胞课上,我有:
@objc func didTapMyButton(sender:UIButton!) {
print("Tapped it!")
}
和
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard isUserInteractionEnabled else { return nil }
guard !isHidden else { return nil }
guard alpha >= 0.01 else { return nil }
guard self.point(inside: point, with: event) else { return nil }
// add one of these blocks for each button in our collection view cell we want to actually work
if self.myButton.point(inside: convert(point, to: myButton), with: event) {
return self.myButton
}
return super.hitTest(point, with: event)
}
在我的单元类初始化我有:
self.myButton.addTarget(self, action: #selector(didTapMyButton), for: .touchUpInside)
我从这里找到了这个py4u.net
尝试了最底层的解决方案。 (据我所知,所有内容都是从这个页面收集的)
在我的情况下,colution 也有效。 然后我只是检查了是否在 xib 和它的contentView
集合视图上检查了User Interaction Enabled
标记。 你猜怎么着。 contentView 的 UserInteraction 被禁用。
启用它修复了按钮 touchUpInside 事件的问题,并且不需要覆盖hitTest
方法。
您可能不必覆盖命中测试或执行上述任何复杂的解决方案。
如果您使用带有大量子视图的自定义按钮,也可能导致此问题。
发生的事情是当按钮被点击测试时,它的一个子视图被返回。
这里一个更简洁的解决方案是在所有按钮的子视图上设置userInteractionEnabled = false
。
这样,点击测试你的按钮只会返回按钮本身,而不返回按钮上的任何视图。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.