当我在手机上玩时,我注意到我的UISegmentedControl响应不是很快。需要2次或更多次尝试才能使我的水龙头注册。所以我决定在Simulator中运行我的应用程序,以更准确地探测出错了什么。通过用鼠标点击几十次,我确定UISegmentedControl的前25%没有响应(下面的屏幕截图中的部分用Photoshop突出显示为红色)。我不知道有什么看不见的UIView可以阻止它。你知道如何使整个控件可以进行处理吗?
self.segmentedControl = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:@"Uno", @"Dos", nil]];
self.segmentedControl.selectedSegmentIndex = 0;
[self.segmentedControl addTarget:self action:@selector(segmentedControlChanged:) forControlEvents:UIControlEventValueChanged];
self.segmentedControl.height = 32.0;
self.segmentedControl.width = 310.0;
self.segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
self.segmentedControl.tintColor = [UIColor colorWithWhite:0.9 alpha:1.0];
self.segmentedControl.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
UIView* toolbar = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.width, HEADER_HEIGHT)];
toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = CGRectMake(
toolbar.bounds.origin.x,
toolbar.bounds.origin.y,
// * 2 for enough slack when iPad rotates
toolbar.bounds.size.width * 2,
toolbar.bounds.size.height
);
gradient.colors = [NSArray arrayWithObjects:
(id)[[UIColor whiteColor] CGColor],
(id)[[UIColor
colorWithWhite:0.8
alpha:1.0
] CGColor
],
nil
];
[toolbar.layer insertSublayer:gradient atIndex:0];
toolbar.backgroundColor = [UIColor navigationBarShadowColor];
[toolbar addSubview:self.segmentedControl];
UIView* border = [[UIView alloc] initWithFrame:CGRectMake(0, HEADER_HEIGHT - 1, toolbar.width, 1)];
border.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
border.backgroundColor = [UIColor colorWithWhite:0.7 alpha:1.0];
border.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[toolbar addSubview:border];
[self.segmentedControl centerInParent];
self.tableView.tableHeaderView = toolbar;
答案 0 :(得分:16)
正如其他答案中所写,UINavigationBar抓住了导航栏本身附近的触摸,但不是因为它有一些扩展到边缘的子视图:这不是原因。
如果您记录整个视图层次结构,您将看到UINavigationBar不会在定义的边上延伸。
接收触摸的原因是另一个:
在UIKit中,有许多“特殊情况”,这就是其中之一。当您点按屏幕时,会启动一个名为“点击测试”的过程。从第一个UIWindow开始,要求所有视图回答两个“问题”:点在你的界限内?什么是必须接收触摸事件的子视图?
这两个方法回答了这个问题:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
好的,现在我们可以继续了。
点击后,UIApplicationMain启动命中测试过程。命中测试从主UIWindow开始(例如,甚至在状态栏窗口和警报视图窗口中执行),并遍历所有子视图。
此过程执行3次:
如果点击导航栏,您会看到UIWindow上的hitTest
将返回UINavigationBar(全部三次)
如果你点击导航栏下面的区域,你会发现一些奇怪的东西:
point - 15
(或类似数字)之类的观点
经过大量的搜索,我发现了这种情况:
UIWindow有一个名为
的(私有)方法-(CGPoint)warpPoint:(CGPoint)point;
调试它,我看到如果它位于状态栏的下方,此方法会更改分接点。 调试更多,我看到堆栈调用使这成为可能,只有3:
[UINavigationBar, _isChargeEnabled]
[UINavigationBar, isEnabled]
[UINavigationBar, _isAlphaHittableAndHasAlphaHittableAncestors]
所以,最后,这个warpPoint
方法检查UINavigationBar是否已启用并且是否可命中,如果是,则“扭曲”该点。该点在0到15之间扭曲了一些像素,当你靠近导航栏时,这种“扭曲”会增加。
既然您知道幕后发生了什么,您必须知道如何避免它(如果您愿意)。
如果应用程序必须在AppStore上运行,则不能简单地覆盖warpPoint:
:这是一种私有方法,您的应用将被拒绝。
你必须找到另一个系统(如建议的那样,覆盖sendEvent,但我不确定它是否会起作用)
因为这个问题很有意思,我明天会考虑一个合法的解决方案并更新这个答案(一个好的起点可以是UINavigationBar的子类,重写hitTest和pointInside,如果在多个调用中给出相同的事件,则返回nil / false)这一点发生了变化。但我必须测试明天是否有效)
修改强>
好的,我尝试了很多解决方案,但要找到合法稳定的解决方案并不简单。 我已经描述了系统的实际行为,在不同的版本上可能会有所不同(hitTest调用多于或少于3次,warpPoint扭曲了大约15px可以改变ecc ecc的点)。
最稳定的显然是UIWindow子类中warpPoint:
的非法覆盖:
-(CGPoint)warpPoint:(CGPoint)point;
{
return point;
}
然而,我发现像这样的方法(在UIWindow子类中)它足够稳定并且可以解决问题:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// this method is not safe if you tap the screen two times at the same x position and y position different for 16px, because it moves the point
if (self.lastPoint.x == point.x)
{
// the points are on the same vertical line
if ((0 < (self.lastPoint.y - point.y)) && ((self.lastPoint.y - point.y) < 16) )
{
// there is a differenc of ~15px in the y position?
// if so, the point has been changed
point.y = self.lastPoint.y;
}
}
self.lastPoint = point;
return [super hitTest:point withEvent:event];
}
此方法记录最后一个点击的点,如果后续点击位于同一个x,并且y最大值为16px,则使用前一个点。 我测试了很多,看起来很稳定。 如果需要,可以添加更多控件以仅在特定控制器中启用此行为,或仅在窗口的已定义部分(ecc ecc)启用此行为。 如果我找到另一个解决方案,我会更新帖子
答案 1 :(得分:1)
我认为问题是因为UINavigationBar中的按钮比正常触摸区域大。见SO post。您还可以通过“UINavigationBar触摸区”Google搜索找到大量有关此问题的讨论。
作为一种可能的解决方案,您可以将分段控件放在导航栏中,但如果符合您的使用案例,您会比我更清楚。
答案 2 :(得分:1)
我想出了一个替代解决方案,对我来说似乎比LombaX更安全。它使用两个事件都带有相同时间戳来拒绝后续事件的事实。
@interface RFNavigationBar ()
@property (nonatomic, assign) NSTimeInterval lastOutOfBoundsEventTimestamp;
@end
@implementation RFNavigationBar
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// [rfillion 2014-03-28]
// UIApplication/UIWindow/UINavigationBar conspire against us. There's a band under the UINavigationBar for which the bar will return
// subviews instead of nil (to make those tap targets larger, one would assume). We don't want that. To do this, it seems to end up
// calling -hitTest twice. Once with a value out of bounds which is easy to check for. But then it calls it again with an altered point
// value that is actually within bounds. The UIEvent it passes to both seem to be the same. However, we can't just compare UIEvent pointers
// because it looks like these get reused and you end up rejecting valid touches if you just keep around the last bad touch UIEvent. So
// instead we keep around the timestamp of the last bad event, and try to avoid processing any events whose timestamp isn't larger.
if (point.y > self.bounds.size.height)
{
self.lastOutOfBoundsEventTimestamp = event.timestamp;
return nil;
}
if (event.timestamp <= self.lastOutOfBoundsEventTimestamp + 0.001)
{
return nil;
}
return [super hitTest:point withEvent:event];
}
@end
答案 3 :(得分:0)
您可能想要检查哪个视图正在录制触摸。试试这个方法 -
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
[touch locationInView:self.view];
if([touch.view isKindOfClass:[UISegmentedControl class]])
{
NSLog(@"This is UISegment");
}
else if([touch.view isKindOfClass:[UITabBar class]])
{
NSLog(@"This is UITabBar");
} else if(...other views...) {
...
}
}
一旦你弄清楚了,你可以缩小你的问题范围。
答案 4 :(得分:0)
看起来您正在使用类别扩展来设置视图的宽度/高度,以及将它们置于父级中心。也许这里有一个隐藏的问题 - 你可以重构这个类别的布局吗?
我将你的代码复制到一个干净的项目中并在UITableViewController的viewDidLoad方法中运行 - 它工作正常,我没有像你报告的死角。我不得不稍微更改您的代码,因为我没有您正在使用的类别扩展名。
此外,如果您在viewDidLoad中运行此代码,则应验证视图是否具有已定义的大小(您可以访问view.width)。如果您以编程方式创建UITableViewController(与nib / storyboard相比),则框架可能是CGRectZero。我的笔芯是从笔尖装入的,因此框架是预设的。
我还会尝试暂时删除边框视图以查看它是否是罪魁祸首。
答案 5 :(得分:0)
我建议您避免在导航栏或工具栏附近使用触敏UI。这些区域通常被称为“slop因子”,使得用户更容易在按钮上执行触摸事件而不会难以执行精确触摸。例如,对于UIButton也是如此。
但是如果你想在导航栏或工具栏接收之前捕获触摸事件,你可以继承子UIWindow并覆盖: - (void)sendEvent:(UIEvent *)event;
答案 6 :(得分:0)
调试此方法的一种简单方法是尝试在项目中使用DCIntrospect。它是一个非常容易使用/实现的库,可以在模拟器中轻松找到哪些视图。
如果突出显示的不是分段视图控制器,那么该视图可能会掩盖触摸事件。
答案 7 :(得分:0)
为UINavigationBar创建一个协议:(添加新文件并粘贴到代码下面)
/******** file: UINavigationBar+BelowSpace.h*******/
"UINavigationBar+BelowSpace.h"
#import <Foundation/Foundation.h>
@interface UINavigationBar (BelowSpace)
@end
/*******- file: UINavigationBar+BelowSpace.m*******/
#import "UINavigationBar+BelowSpace.h"
@implementation UINavigationBar (BelowSpace)
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
int errorMargin = 5;// space left to decrease the click event area
CGRect smallerFrame = CGRectMake(0 , 0 - errorMargin, self.frame.size.width, self.frame.size.height);
BOOL isTouchAllowed = (CGRectContainsPoint(smallerFrame, point) == 1);
if (isTouchAllowed) {
self.userInteractionEnabled = YES;
} else {
self.userInteractionEnabled = NO;
}
return [super hitTest:point withEvent:event];
}
@end
希望这有帮助^ ^
答案 8 :(得分:-1)
试试这个
self.navigationController!.navigationBar.userInteractionEnabled = false;