Discussion:
NSScrollView and NSTrackingArea woes
João Varela
2015-07-15 10:08:26 UTC
Permalink
Hi,

I am implementing two custom classes, one of them is the document view inside an NSScrollView and the other is a subview of the document view. I need to create two NSTrackingArea’s to display the appropriate cursor when the mouse hovers over specific areas of the subviews. As instructed by Apple, I implemented an -updateTrackingAreas method to update the tracking areas when needed. Everything works as expected if the window and its views and subviews are resized or if the app is re-activated or the window becomes the key window again. Everything works well too when the user scrolls up or down to the end of the document view. The NSTrackingArea’s are updated correctly and the -updateTrackingAreas method is called in all these occasions.

However, none of this happens when the user only scrolls (up or down) a little and does not reach the top or bottom of the document view. In this case, not only the -updateTrackingAreas method of the subview is not called, but also the cursor is no longer updated properly, because the NSTrackingArea’s of the subviews have not updated correctly. It seems as though I am not the only one getting bitten by this problem. See here:

http://stuccy.com/questions/17731292/mouseovers-in-nscollectionviewnsscrollview <http://stuccy.com/questions/17731292/mouseovers-in-nscollectionviewnsscrollview>

I tried to circumvent this problem by detecting when the origin.y of the document view rect is not changing anymore within the NSScrollView (this is not easy on OS X because there is no notification to tell us when scrolling ended if you want to support OS X 10.8). For this purpose, I added a method in a NSScrollView subclass that does just that:

- (void)updateTrackingAreas
{
if (_y != self.documentVisibleRect.origin.y)
{
_y = self.documentVisibleRect.origin.y;

if (!_isScheduled)
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(),
^{
if (_y != self.documentVisibleRect.origin.y)
{
[self updateTrackingAreas];
}
else
{
[_documentView updateTrackingAreas];
}

_isScheduled = NO;
});

_isScheduled = YES;
}
}
}

This method is called every time the user uses the scroll wheel as it is invoked inside the -scrollWheel method of the NSScrollView subclass. It works as expected and coalesces all the scroll wheel events into just one call. When the scrolling stops, indeed the [_documentView updateTrackingAreas] is called, which then invokes the subview’s -updateTrackingAreas method. This should do the trick, but it doesn’t.

Thus, before I try the solution given in the link above, which I find even more hacky than my own solution, is there something that I am missing? Why wouldn’t the NSTrackingArea’s update correctly when they do so if the window is resized or becomes key again or the user scrolls up or down to the end? What is the OS doing in these situations that I am not doing here?

I would appreciate any feedback,

J. Varela
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.nark
Muthulingam Ammaiappan
2015-07-15 10:44:26 UTC
Permalink
Hi J.Varela,


i too faced the same problem... and i found that "inertial scrolling" has
the reason for this misbehaviour.
so "disable the inertia scrolling" will solve this problem.

just add the following line to your application init method...

* [[NSUserDefaults standardUserDefaults] setBool:NO
forKey:@"AppleMomentumScrollSupported"];//disable
the inertia scrolling *


Thanks & Regards,

Muthu
Post by João Varela
Hi,
I am implementing two custom classes, one of them is the document view
inside an NSScrollView and the other is a subview of the document view. I
need to create two NSTrackingArea’s to display the appropriate cursor when
the mouse hovers over specific areas of the subviews. As instructed by
Apple, I implemented an -updateTrackingAreas method to update the tracking
areas when needed. Everything works as expected if the window and its views
and subviews are resized or if the app is re-activated or the window
becomes the key window again. Everything works well too when the user
scrolls up or down to the end of the document view. The NSTrackingArea’s
are updated correctly and the -updateTrackingAreas method is called in all
these occasions.
However, none of this happens when the user only scrolls (up or down) a
little and does not reach the top or bottom of the document view. In this
case, not only the -updateTrackingAreas method of the subview is not
called, but also the cursor is no longer updated properly, because the
NSTrackingArea’s of the subviews have not updated correctly. It seems as
http://stuccy.com/questions/17731292/mouseovers-in-nscollectionviewnsscrollview
<
http://stuccy.com/questions/17731292/mouseovers-in-nscollectionviewnsscrollview
I tried to circumvent this problem by detecting when the origin.y of the
document view rect is not changing anymore within the NSScrollView (this is
not easy on OS X because there is no notification to tell us when scrolling
ended if you want to support OS X 10.8). For this purpose, I added a method
- (void)updateTrackingAreas
{
if (_y != self.documentVisibleRect.origin.y)
{
_y = self.documentVisibleRect.origin.y;
if (!_isScheduled)
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(),
^{
if (_y != self.documentVisibleRect.origin.y)
{
[self updateTrackingAreas];
}
else
{
[_documentView updateTrackingAreas];
}
_isScheduled = NO;
});
_isScheduled = YES;
}
}
}
This method is called every time the user uses the scroll wheel as it is
invoked inside the -scrollWheel method of the NSScrollView subclass. It
works as expected and coalesces all the scroll wheel events into just one
call. When the scrolling stops, indeed the [_documentView
updateTrackingAreas] is called, which then invokes the subview’s
-updateTrackingAreas method. This should do the trick, but it doesn’t.
Thus, before I try the solution given in the link above, which I find even
more hacky than my own solution, is there something that I am missing? Why
wouldn’t the NSTrackingArea’s update correctly when they do so if the
window is resized or becomes key again or the user scrolls up or down to
the end? What is the OS doing in these situations that I am not doing here?
I would appreciate any feedback,
J. Varela
_______________________________________________
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
https://lists.apple.com/mailman/options/cocoa-dev/muthulingam.a%40gmail.com
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email se
João Varela
2015-07-17 00:33:27 UTC
Permalink
Post by Muthulingam Ammaiappan
Hi J.Varela,
i too faced the same problem... and i found that "inertial scrolling" has the reason for this misbehaviour.
so "disable the inertia scrolling" will solve this problem.
just add the following line to your application init method...
Thanks & Regards,
Muthu
Thanks, Muthu, it really solved the problem I was seeing, but the scrolling is not smooth at all with that option off. Thus, I really have to find another solution.

_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sent to ***@ml-in.narkive.ne
Quincey Morris
2015-07-15 17:28:33 UTC
Permalink
Your assumption about *when* updateTrackingAreas will be called is incorrect. Depending on the way you create the tracking area, and the amount of the scroll, the tracking area might not need to be changed at all.

So, there are at least three issues you need to resolve:

1. Are you setting up the tracking area properly? For example, are you specifying the area in the correct coordinate system? Are you using suitable options?

2. When you scroll the view, what *actually* happens to the tracking area? For this, you’re not interested so much in what happens immediately to the cursor, but rather whether moving the cursor away and back again will “find” the tracking area, either in the correct place or in an incorrect place.

3. The clip view (the view between the scroll view and the document view in the hierarchy) also has a tracking area, and the scroll view also has a “documentCursor” property. Thus, other views may explicitly change the cursor at unexpected times. You need to investigate whether the tracking area is incorrect, or whether the cursor is just getting re-set to something else.

Unfortunately, this is hard to debug, because debugging changes the state of your window and the position of the cursor.




_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This
João Varela
2015-07-17 01:19:57 UTC
Permalink
This post might be inappropriate. Click to display it.
Ken Thomases
2015-07-17 01:37:29 UTC
Permalink
Post by João Varela
I’m passing [self bounds]. And no, I am not using NSTrackingInVisibleRect
Why aren't you using that? That seems like the most obvious way to get correct behavior. The only reason you wouldn't use it is if you were adding a tracking area that was only for a portion of a view, but you say you're using the view's bounds.

That might make the whole issue go away. (Or it might not.)

Does your implementation of -updateTrackingAreas call through to super? The docs say it should. I notice that your coalescing implementation did not.
Post by João Varela
Yes, the tracking area is incorrect and apparently I cannot invoke -updateTrackingAreas myself because it apparently messes up with the NSTrackingArea’s of the subviews and most likely of the clipView, etc.
So I guess this problem could be solved if there were a method to tell the OS that the tracking areas are invalid (and thus they need to be updated). However, apparently there is none, AFAICT. Or is there?
You can't invoke -updateTrackingAreas yourself, but there's nothing preventing you from removing old tracking areas and adding new ones, as appropriate, any time you like. You can put that code in a method of your own which is called by -updateTrackingAreas plus any other time you want.

Regards,
Ken


_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This emai
João Varela
2015-07-17 01:58:53 UTC
Permalink
Post by Ken Thomases
Post by João Varela
I’m passing [self bounds]. And no, I am not using NSTrackingInVisibleRect
Why aren't you using that? That seems like the most obvious way to get
correct behavior. The only reason you wouldn't use it is if you were
adding a tracking area that was only for a portion of a view, but you say
you're using the view's bounds.
Passing self.bounds it is the simpler case I am using to test this problem.
Indeed I need to create two tracking areas that do not coincide with the
subview's bounds.

That might make the whole issue go away. (Or it might not.)
Post by Ken Thomases
Unfortunately it does not solve that, because I tried that option and the
results are the same.
Post by Ken Thomases
Does your implementation of -updateTrackingAreas call through to super?
The docs say it should. I notice that your coalescing implementation did
not.
You are right. I included such call to the super at the beginning or at the
end, and yes the same problem remains.
Post by Ken Thomases
Post by João Varela
Yes, the tracking area is incorrect and apparently I cannot invoke
-updateTrackingAreas myself because it apparently messes up with the
NSTrackingArea’s of the subviews and most likely of the clipView, etc.
Post by João Varela
So I guess this problem could be solved if there were a method to tell
the OS that the tracking areas are invalid (and thus they need to be
updated). However, apparently there is none, AFAICT. Or is there?
You can't invoke -updateTrackingAreas yourself, but there's nothing
preventing you from removing old tracking areas and adding new ones, as
appropriate, any time you like. You can put that code in a method of your
own which is called by -updateTrackingAreas plus any other time you want.
Hmm, that I haven't tried. If that works you'll be my hero.
Post by Ken Thomases
Regards,
Ken
Thanks, João
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sent to ***@ml-in.narkive.ne
d***@gmail.com
2015-07-17 03:31:23 UTC
Permalink
Why not just observe bounds change notifications from the clipview?
Then handle those with the update call.

Sent from my iPhone
Post by João Varela
Post by Ken Thomases
Post by João Varela
I’m passing [self bounds]. And no, I am not using NSTrackingInVisibleRect
Why aren't you using that? That seems like the most obvious way to get
correct behavior. The only reason you wouldn't use it is if you were
adding a tracking area that was only for a portion of a view, but you say
you're using the view's bounds.
Passing self.bounds it is the simpler case I am using to test this problem.
Indeed I need to create two tracking areas that do not coincide with the
subview's bounds.
That might make the whole issue go away. (Or it might not.)
Post by Ken Thomases
Unfortunately it does not solve that, because I tried that option and the
results are the same.
Post by Ken Thomases
Does your implementation of -updateTrackingAreas call through to super?
The docs say it should. I notice that your coalescing implementation did
not.
You are right. I included such call to the super at the beginning or at the
end, and yes the same problem remains.
Post by Ken Thomases
Post by João Varela
Yes, the tracking area is incorrect and apparently I cannot invoke
-updateTrackingAreas myself because it apparently messes up with the
NSTrackingArea’s of the subviews and most likely of the clipView, etc.
Post by João Varela
So I guess this problem could be solved if there were a method to tell
the OS that the tracking areas are invalid (and thus they need to be
updated). However, apparently there is none, AFAICT. Or is there?
You can't invoke -updateTrackingAreas yourself, but there's nothing
preventing you from removing old tracking areas and adding new ones, as
appropriate, any time you like. You can put that code in a method of your
own which is called by -updateTrackingAreas plus any other time you want.
Hmm, that I haven't tried. If that works you'll be my hero.
Post by Ken Thomases
Regards,
Ken
Thanks, João
_______________________________________________
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
https://lists.apple.com/mailman/options/cocoa-dev/dangerwillrobinsondanger%40gmail.com
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.n
João Varela
2015-07-19 15:36:37 UTC
Permalink
First of all I would like to thank everybody that tried ho give me a hand. I really do appreciate it.
I also want to write this email to let people know how I solved the problem, so that the readers of this list do not have to go through a week of frustration as I have. Well, it took so long because this is a side project, and I am not working on it all the time.

For those who did not follow this thread from the beginning, the problem (bug) that I was hitting is that setting up NSTrackingArea objects in the document view of an NSScrollView works as expected except when the user begins to scroll and the inertia scrolling option is on. A way to avoid this is to turn off inertia scrolling as Muthu suggested, but then the scrolling is jumpy at best.

Here are my conclusions:

1) Using NSTrackingArea objects is fine and dandy as long as you do not change the superview and its subviews and you do not place them in a scrolling view or you do not have to add and remove subviews, sometimes in a very dynamic way as I had to do (because I wanted a scenario of scrolling vertically, but also horizontally, and when the latter case, the old subviews had to be removed and new ones had to be created and added to the document view programatically). To manage the tracking areas of all the subviews that came and went or that were in a scrolling view in such a scenario is really a nightmare. I can understand that, because NSTrackingArea objects were not designed to handle such dynamic scenarios. Apparently, there are exceptions to this. I checked several example of sample code by Apple that NSTrackingArea objects can be used quite well even in a scrolling view as along as they are in an NSTableView or NSOutlineView (for that I highly recommend that you check the PhotoSearch and the HoverTableViewRow sample code).

2) Using NSTrackingArea with the option NSTrackingCursorUpdate and overriding cursorUpdate does not work well in small rects (e.g. heights around 7.0 or less).

3) I learned that you do not have to override -updateTrackingAreas for the NSTrackingArea objects to update in some scenarios. You can check this in the HoverTableViewRow sample code by Apple, which uses this method to set up the tracking area once without any further work from the developer, which goes against the sample code given by the documentation. I tried it in my code and it worked although it did not solve the bug I was hitting.

4) After experimenting many, many scenarios suggested by Ken, Quincey and John, I find out that that the only way to make everything work as I intended was to create a unique NSTrackingArea in the document view and then using mouse-moved events inside this rect to find the subview and then dispatch this event to it. This solved all my problems and now the tracking is correct even with the inertia scrolling on.

I hope this will help someone else in the future.
Post by d***@gmail.com
Why not just observe bounds change notifications from the clipview?
Then handle those with the update call.
Sent from my iPhone
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sen

Quincey Morris
2015-07-17 03:20:46 UTC
Permalink
Post by João Varela
NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved|
NSTrackingActiveInActiveApp;
Apart from the things Ken said, I’m struck by the above. So you’re *not* using ‘NSTrackingCursorUpdate’? Are you overriding ‘cursorUpdate:’ or not?

While it’s feasible to drive cursor updating from mouse entered/exited events, it’s quite a different problem for several subtle reasons. It’s probably time for you to show code — this guessing based on partial descriptions of what you’re doing isn’t going to get us very far.



_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sent to ***@m
Loading...