In Mavericks, Apple introduced responsive scrolling architecture in NSScrollView. This feature adds a smart way for generating overdraws and decouples handling of the incoming scroll events from the main event loop into a separate one. This topic is described in detail in the session 215 from WWDC 2013.
Because of the different event model, the scroll events no longer go through the scrollWheel(with:) method. In fact, if you override this method in the NSScrollView subclass, you opt-out from the responsive scrolling architecture completely and fall back to the legacy model.
In my NSScrollView I'd like to implement an interaction for magnification using scrolling while holding the command key. This can be observed in MindNode or OmniGraffle apps. The standard way to do this would be overriding scrollWheel(with:) and checking the modifierFlags on each scroll event. As described above, this would cause an opt-out from the responsive scrolling model.
I wonder whether there is a way to implement this interaction while also preserving the responsive scrolling?
What I have already achieved/tried:
In my NSScrollView subclass I have overridden scrollWheel(with:) and am returning true from the static property isCompatibleWithResponsiveScrolling in order to force the participation in responsive scrolling.
This way I am able to check the first scroll event for the modifier flags. If the command key is NOT pressed, I simply pass the event to super and let the NSScrollView do its thing. If the command key is pressed, I go a different route and track next scroll events on the window to do the magnification.
The problem is when one of these tracking loops is running and the user changes the press state of the command key (either presses it or releases it).
The switch from magnification to scrolling (on command key release) is simple, since the tracking loop is fully under my control.
The switch from scrolling to magnification (on command key press) is more tricky, because I cannot check the scroll events. I have overridden flagsChanged(with:) and can observe when this moment happens but I have not found a way to end the scrolling. This SO question asks about ending/disabling scrolling but has no answer yet.
I’m going to answer here instead of in comments, since I have more data.
There are two issues with “Responsive scrolling”:
(1) A few years ago there was a bug — a difference in how accelerated responsive scrolling was vs. legacy scrolling. By accelerated I mean the distance the document would travel for any given amount of movement of the fingers on the trackpad — legacy scrolling didn’t move the document as much so it felt sluggish. I reported this and it seems to have been fixed in 10.14, at least.
⑵ As near as I can tell from my research and talking to Apple people “Responsive scrolling” was designed to make scrolling more consistent even when an application is hogging the main thread (which would normally block the flow of events) — it processes scroll wheel events on a background thread instead and then messages the main thread with them. Since this still involves the main thread being called per scrolling event (or scrolling events being coalesced) I’m not clear what situations this is a win in. There may be some extra magic where the scrollview will draw more of the document from the cached content of you implement the optional caching stuff.
In my app I’m actually using the scrollview with a dummy (transparent) document and I take its scroll events and move around a SceneKit camera. Since my scene changes dramatically as you scroll around there’s no benefit to trying to pre-render outside the visible rect.
So in my case I no longer perceive any performance difference between responsive and legacy scrolling (although I did back when I filed the bug).
Related
I need a control
It should be asynchronous (should not block the launching thread).
It should block all the user interactions with background screen.
Backdrop shadow on background so that it gives the feeling of disabled interactions with background.
I tried wxBusyInfo. It solves my first requirement, but one can interact with background window.
Then I use the wxWindowDisabler with wxBusyInfo, It disables the background window. But looks like events are getting buffered while disabler is on and when I am destroying wxWindowDisabler object, buffered events are firing.
Then I found wxEventBlocker that can be used to block the events.
Is this the right way to achieve the requirements?
wxBusyInfo is the right class to do what you want, it doesn't dispatch events, so I'm not sure how do you manage to interact with anything while it is shown. Because it doesn't dispatch events -- even the repaint ones -- it's not recommended to show it for relatively long periods of time. If you need something more permanent, use wxProgressDialog.
Finally, wxEventBlocker is a rather special class which is only useful in very specific circumstances and this is not one of them.
It seems that my view-based OutlineView try to re-render text every time user scrolling. I think that was because it tried to save memory by re-using rows that fall outside of visible rectangle, then re-render new data to those old views. How can I disable this? I'm willing to sacrifice memory to archive this so that I can get better scrolling performance. (I have tried with various optimize like layer or so, but no luck).
EDIT: There is another question with same purpose as mine here How to make NSTableView not reuse TableCellViews, but no answer yet :(
I think you are confused -- it doesn't re-render the text. The table only shows views for the visible area (caveat below), and pulls in new views as you scroll -- potentially re-using old views.
You provide the view. Use the delegate method viewForTableColumn:row: and return your own cached view for a given row.
But that isn't going to prevent drawing; the view will likely still get marked dirty and draw.
I don't think you are asking the right question. It sounds like you have a performance problem. To help you with that, we'd have to see samples or instrument traces.
Caveat: Responsive Scrolling will pull in views that are in the non visible area. See the AppKit release notes about this.
corbin
I've got an NSCollectionView which houses three columns of NSCollectionViewItems. In each of the NSCollectionViewItem prototype views, I have a single NSImageView. As far as I'm aware, this is a fairly standard setup. I think the problem stems from my use of a popover to present the collection view, but I'm honestly not sure. Basically, the problem I'm having is that when I click on one of the aforementioned NSCollectionViewItems to mark it as "selected," every once in awhile it flickers/flashes. It's not a total deal-breaker, but it's annoying enough to make me post this question.
Coming from the HTML/CSS/JS game, I started to think of all the ways I would prevent the flicker when building stuff with web technologies. My first thought was to make certain parts of the NSCollectionView (and subviews) layer-backed. I figured that by doing the drawing via layers or just setting the collection view to be layer-backed period, it would render better, kind of like GPU-enhanced animations in CSS (e.g. no sub-pixel antialiasing, enables z-depth, etc.).
So, I subclassed NSCollectionView and added a [self setWantsLayer:YES] in the
-(id)initWithCoder:aDecoder method. I was right! It removed the flickering altogether. Also, the scrolling became much smoother. However, in the process of making the flicker go away, everything else started messing up. The popover would take about 10s to load (vs. ~500ms originally), items would do weird overlapping things, etc.
My question to you all is this: what should I do? If anyone could help a brother out, I'd really appreciate it.
I think this is a new spin on an old question, but I'm completely stuck here.
In my app, I have a UITableView with 650 cells, each with a custom 16x16 RGB icon. On most recent iOS devices, loading all of those icons into memory before displaying the table works totally fine, but on older hardware, I'd like to implement a lazy load system that only loads icons it needs.
I've implemented the Apple LazyTableImages example, (which uses a UIScrollView delegate to determine when the table stops moving to load the visible icons), but I've run into another snag.
My UITableView also has a section index display (ie the list of labels on the right hand side you can swipe up and down to scroll quickly), and the LazyTableImages example hasn't taken this into account.
If I scroll using the index, the images won't lazy-load. :(
As far as I can see, the scroll index doesn't actually have any delegate events it triggers.
So I'm wondering, has anyone else tried to implement lazy-loading on a table with a scroll index? Is there any way to track the index and find out if the user has interacted with it?
Thanks!
After buzzing around a few of my iOS developer buddies, I came up with a solution that worked well enough.
I set it up so that in addition to the icons being loaded from the UIScrollView delegates, an NSTimer object will periodically call a method that checks the currently visible table cells ([UITableView indexPathsForVisibleRows]) every .5 seconds, and loads any icons on the screen that haven't been loaded yet in a single separate thread.
I tried to make the solution as efficient as possible, so I made sure the timer was only active when the tableView was visible and stationary, and I liked it since it meant that every visible icon regardless was addressed.
One thing I discovered was that if the tableView was reloaded while the thread was looping through the visible cells (rare, but was possible), it would crash. The solution to this was to make sure each cell data source entry was retained while the icon was being loaded.
I've created an application for Mac that uses a horizontal resize view (technically a BWToolkit resize view) that is linked to a toggleCollapse button. After collapsing and expanding the panel a few times, this happens (see image) right table-view should be flush with panel divider. Any one have any info on why this is happening. I've carefully adjusted my alignment and anchors to be correct but it seems like this is a weird issue that bugs out the view?
EDIT: Is anyone NOT having this issue? Any input would be helpful! Maybe I just need to start fresh?
This is a known bug in BWSplitView itself, and is related to how view animation and timers interact in that view. The details are covered here:
http://bwalkin.lighthouseapp.com/projects/36323/tickets/34-split-view-doesnt-resize-subview-properly-during-a-toggle-uncollapse-after-manual-collapse
Basically, before the view is animated, autoresizesSubviews is unset, so that the view can be collapsed/expanded without affecting the contents; a timer is then scheduled to restore the autoresizesSubviews property for when the animation has completed. But (of course) the animation may sometimes finish ever so slightly earlier, or later, than the scheduled timer fires; so autoresizing is switched on before the animation has completed, resulting in a few pixels resize. As you've probably seen, the subviews tend to move around a bit as the view is collapsed and uncollapsed.
The "full" fix for this would be to restore autoresizing when the CAAnimation has completed using a callback, but this hasn't been implemented yet. However, in that thread "Robert Payne" has posted a version of BWSplitView.m which uses a slightly different approach - the view sizes are recorded before collapsing, and restored after expanding. I applied this patch myself (I think it won't compile at first - but it's a simple matter of an undeclared variable?) and can confirm it does fix the problem.
Let me know if you'd like a BWSplitView.m which compiles successfully, or a copy of the compiled framework.