我有一个问题,我认为可以解决一些hackery,但我很好奇,如果有一个更简单的方法来完成工作而不必完成所有这些。
我有一堆NSView(层支持,如果以某种方式帮助提供更好的解决方案),如下所示:
这里的事情是,它本质上是一个菜单,但是悬停敏感。如果用户将鼠标悬停在较低级别视图的某个公开部分上,我需要根据视图的内容执行操作。它是一个动态系统,因此像这样堆叠的菜单项的数量可能会改变,使静态计算更加困难。正如您所看到的,它们基本上都是第一个项目的副本(形状),但是通过简单的变换旋转,您可以进一步向下旋转一点。
我对SO社区的问题是你们都认为获得mouseEntered的最佳方法是什么:和mouseExited:这些视图的字面可见部分的事件?
我试图做的是在这些视图的visibleRect部分使用NSTrackingArea,这听起来比在这种情况下更实用。实际上,visibleRect似乎始终对所有这些人都是“可见的”。除了部分重叠的NSView之外,没有任何东西被明确阻止或隐藏。所有这一切都是我从所有视图中得到一个垃圾邮件控制台,一旦鼠标进入他们的矩形就会尖叫。
我正在考虑的是制作每个菜单项的子NSView,并让每个菜单项负责跟踪区域......每个菜单项都有一个可以报告的右侧和底侧的“条带”视图,但是这仍然是一个黑客,是icky。
有没有人有更好的主意?也许是经验之一?
谢谢!
答案 0 :(得分:3)
我知道你已经有了一个解决方案,但我想我会尝试一种不同的方法,这不需要获得大量的mouseMoved事件。我在代码中创建了3个自定义视图,为它们添加了跟踪请求,并将所有mouseEntered和mouseExited消息发送到执行hitTest的相同方法,以确定哪个视图是最顶层的。这是窗口内容视图的代码。
@implementation MainView
@synthesize oldView;
-(void)awakeFromNib {
oldView = nil;
Card *card1 = [[Card alloc]initWithFrame:NSMakeRect(150, 150, 200, 150) color:[NSColor redColor] name:@"Red Box"];
NSTrackingArea *area1 = [[NSTrackingArea alloc]initWithRect:card1.frame options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:area1];
[self addSubview:card1];
Card *card2 = [[Card alloc]initWithFrame:NSMakeRect(180, 120, 200, 150) color:[NSColor yellowColor] name:@"Yellow Box"];
NSTrackingArea *area2 = [[NSTrackingArea alloc]initWithRect:card2.frame options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:area2];
[self addSubview:card2];
Card *card3 = [[Card alloc]initWithFrame:NSMakeRect(210, 90, 200, 150) color:[NSColor greenColor] name:@"Green Box"];
NSTrackingArea *area3 = [[NSTrackingArea alloc]initWithRect:card3.frame options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:area3];
[self addSubview:card3];
}
-(void)mouseEntered:(NSEvent *)theEvent {
[self reportTopView:theEvent];
}
-(void)mouseExited:(NSEvent *)theEvent {
[self reportTopView:theEvent];
}
-(void)reportTopView:(NSEvent *)theEvent {
id topView = [self hitTest:[theEvent locationInWindow]];
if (![topView isEqual:oldView]) {
oldView = topView;
([topView isKindOfClass:[Card class]])? NSLog(@"%@",[(Card *)topView name]):NULL;
}
}
这是我所谓的卡片(彩色矩形)的代码:
@implementation Card
@synthesize name,fillColor;
- (id)initWithFrame:(NSRect)frame color:(NSColor *)color name:(NSString *)aName{
self = [super initWithFrame:frame];
if (self) {
self.fillColor = color;
self.name = aName;
}
return self;
}
- (void)drawRect:(NSRect)rect {
[self.fillColor drawSwatchInRect:rect];
}
答案 1 :(得分:1)
我终于通过Steven Troughton-Smith在Twitter上找到了解决方案。以下是它的工作原理:
在每个菜单项中,我忽略了与NSTrackingArea或直接鼠标位置解释相关的任何内容。相反,父控制器视图正在处理所有跟踪和接收鼠标移动事件。
每个菜单项都有一个重写的hitTest:方法,它执行点转换并返回被测点是否在背景图像内(那里有阴影和东西,使得它比vanilla实现更难)。
然后我在控制器中设置了一种“悬停菜单项已更改”回调,以便我可以处理悬停菜单更改。
这是一个非常简单的解决方案。很高兴我决定停下来问问,而不是和我以前的想法一起破解。
谢谢史蒂文!
答案 2 :(得分:1)
你所要做的就是从你所处的视图中获取hitTest。如果是这样的话:
window.view.hitTest(window.mousePos) === self/*sudo code*/
此代码的作用是返回鼠标位置下的视图。现在,您只需设置一些“if”和“else”子句来验证鼠标是否已关闭或视图上。
完整代码示例:
https://gist.github.com/eonist/537ae53b86d5fc332fd3
此概念的完整描述:(perma link)
http://stylekit.org/blog/2015/12/20/Overlapping-tracking-areas/
答案 3 :(得分:0)
我不得不为这个问题添加另一个答案,因为这是解决问题的另一种方法。此方法现在还包括路径断言(认为具有圆边或其他自定义路径的rects)
答案很长,但确实有效:
http://stylekit.org/blog/2016/01/28/Hit-testing-sub-views/
它涉及使用苹果提供的方法:CGPathContainsPoint(path,transform,point)
如果您点击该博客文章的链接,然后从那里检查github上的styleKit回购。你会发现代码需要实现上面给出的gif动画示例。我提供这个作为答案的指针,因为它可能比你自己研究这个时间少得多。我在所有的UI元素中使用这种技术,它可以完美地工作。