{"id":64,"date":"2005-12-05T12:05:46","date_gmt":"2005-12-05T19:05:46","guid":{"rendered":"http:\/\/www.red-sweater.com\/blog\/?p=64"},"modified":"2005-12-12T09:47:06","modified_gmt":"2005-12-12T16:47:06","slug":"throttling-nsslider","status":"publish","type":"post","link":"https:\/\/redsweater.com\/blog\/64\/throttling-nsslider","title":{"rendered":"Throttling NSSlider"},"content":{"rendered":"<style type=\"text\/css\"><!-- .caption { border-style:dashed; border-width:1px; border-color:#BBBBBB; margin-left:20px; padding:10px;}--><\/style>\n<p>Suppose you want to throttle the rate at which a continuous NSSlider sends its action. To save you from the same fate I met, I will talk you through some of the avenues you might pursue, and ultimately describe the simple solution I adopted which is both easiest and is the only solution I know of that actually gets the job done.<\/p>\n<p>\nSearching the documentation, you might discover the very intriguing &#8220;getPeriodicDelay:interval:&#8221; method of NSCell. The description of this method in the Apple documentation is as follows:\n<\/p>\n<div class=\"caption\">\nReturns initial delay and repeat values for continuous sending of action messages to target objects.\n<\/div>\n<p>\nSounds like a winner! Sigh. It&#8217;s not.\n<\/p>\n<p>\nThe problem, as I&#8217;ve deduced after several hours of beating my head against a wall, wondering whether I am in fact not cut out for a programming career after all, is that &#8220;continuous&#8221; means two distinct things, depending on the type of NSCell you are dealing with. The documentation rarely addresses this, and even misleads us with <a href=\"http:\/\/developer.apple.com\/documentation\/Cocoa\/Conceptual\/ControlCell\/Tasks\/UsingContControl.html\">documentation like this<\/a> where outright promises are made about the relationship between &#8220;continuous actions&#8221; and the periodic delay attribute.\n<\/p>\n<p>\nThe two categories of continuous controls in Cocoa are <strong>&#8220;periodic&#8221;<\/strong> and <strong>&#8220;send on drag&#8221;<\/strong>. Hints at this distinction in the documentation are few and far between, but if you look in the description of NSCell&#8217;s &#8220;sendActionOn:&#8221; method, you&#8217;ll see this line (emphasis mine):\n<\/p>\n<div class=\"caption\">\nYou can use setContinuous: to turn on the flag corresponding to NSPeriodicMask or NSLeftMouseDraggedMask, <em>whichever is appropriate to the given subclass of NSCell<\/em>.\n<\/div>\n<p>\nWhat does it mean by this? It means that controls whose cells are continuous in a user-driven nature, like a user tweaking a knob or pushing a slider, shouldn&#8217;t send their value continuously when the user is not &#8230; uh, tweaking. Controls like NSButton, which are simply pressed as the user tracks them, need some stimulation for the periodic send: a periodic timer. Controls like NSSlider can rely on the user to move the mouse when the action needs to be sent.\n<\/p>\n<p>\nUnfortunately, what I&#8217;ve discovered is that the timing of &#8220;send on drag&#8221; controls is completely un-throttled. The control&#8217;s cell appears to send out actions as fast as the user can generate drag events. If drag events were only generated every pixel or whatever, that would be fine (* See End of Entry), but drag events come pouring through your application as the user makes the slightest nudge of their pointing device. Depending on what you&#8217;re doing in your slider response, this might be highly undesirable.\n<\/p>\n<p>\nIdeally, I would expect that &#8220;send on drag&#8221; cells should <em>also<\/em> limit their sending to the interval identified by the getPeriodicDelay:interval: method.  &#8220;Send on drag&#8221; cells should be an optimization of &#8220;periodic&#8221; cells, not a completely new implementation.  This would make all of Apple&#8217;s documentation correct. As it is, most of Apple&#8217;s documentation having to do with &#8220;continuous controls and periodic events&#8221; is pretty misleading if not downright incorrect.<\/p>\n<h4>Working Around the Problem<\/h4>\n<p>It turns out that this problem can be worked around in a pretty simple way that doesn&#8217;t even require subclassing the associated control cell. It&#8217;s too bad that the documentation was confusing and nothing worked like it said it would(*), but now that I&#8217;ve come to this solution, I&#8217;m happy it&#8217;s the simplest of them all.\n<\/p>\n<div class=\"caption\">Easy Solution: Throttle continuous &#8220;send on drag&#8221; controls by overriding &#8220;sendAction:to:&#8221;.<\/div>\n<p>Instead of limiting the frequency of the &#8220;send&#8221; action, just nip it in the bud. Whenever the cell for your control ultimately decides it&#8217;s ready to send an action, it goes through the sendAction:to: method. By subclassing the control and placing a date throttle on the method, you can achieve whatever throttling you feel is appropriate.\n<\/p>\n<p><strong>Note:<\/strong> Several readers noticed a shortcoming in the original version: the final value was often not sent because the throttling prevented it from being sent when the &#8220;expiration date&#8221; had not yet arrived. Special thanks to <a href=\"http:\/\/www.nuthole.com\/\">Jack Nutting<\/a> for observing that a quick check of the current event type would solve the problem nicely. The only reservation I have about this is whether there are automated ways of manipulating a slider that might not involve mouse ups and downs. Does anybody know if this is possible?\n<\/p>\n<p>\nThe code below has been updated to include a check for this &#8220;final value&#8221; condition. Let me know if anybody spots other problems that should be remedied!<\/p>\n<div class=\"caption\">\n<pre>\n\tstatic NSTimeInterval kTimeBetweenSends = 0.2;\t\/\/ 1\/5 second\n\n\t- (BOOL)sendAction:(SEL)theAction to:(id)theTarget\n\t{\n\t\tBOOL handleEvent = NO;\n\t\t\n\t\t\/\/ Always send action when the mouse comes up. This means that the final \n\t\t\/\/ send is probably coming through, which we don't want to miss!\n\t\tif ([[NSApp currentEvent] type] == NSLeftMouseUp)\n\t\t{\n\t\t\thandleEvent = YES;\n\t\t\t\n\t\t\t\/\/ We also want to clear the \"next time\" so \n\t\t\t\/\/ we start fresh if the user quickly clicks again\n\t\t\t[mNextSendTime release];\n\t\t\tmNextSendTime = nil;\n\t\t}\n\t\telse \n\t\t{\t\n\t\t\t\/\/ Only pass actions on if we're not waiting for a throttling date to pass\n\t\t\tNSDate* nowDate = [NSDate date];\n\t\t\tif ((mNextSendTime == nil) || ([mNextSendTime compare:nowDate] == NSOrderedAscending))\n\t\t\t{\t\t\n\t\t\t\thandleEvent = YES;\n\t\t\t}\n\t\t}\n\t\t\n\t\tif (handleEvent == YES)\n\t\t{\n\t\t\thandleEvent = [super sendAction:theAction to:theTarget];\n\n\t\t\t[mNextSendTime release];\n\t\t\tmNextSendTime = [[NSDate dateWithTimeIntervalSinceNow:kTimeBetweenSends] retain];\n\t\t}\n\t\t\n\t\treturn handleEvent;\n\t}\n<\/pre>\n<\/div>\n<p>\nSo if you find yourself in my position someday, my advice is this: ignore Apple&#8217;s documentation on this issue and implement something like the above instead. Easier to subclass, easier to inject, and it actually works.<\/p>\n<p><strong>Update:<\/strong> If you&#8217;re inside Apple and are wondering whether I wrote a bug or not, here it is: <a href=\"rdar:\/\/problem\/4365583\">4365583<\/a>.<\/p>\n<p><strong>* Update 2:<\/strong> When I said it would be &#8220;OK&#8221; if it only sent the value when the pixel changes, I was wrong. It&#8217;s not OK. In fact, that&#8217;s what it does. Kudos to Apple!  But&#8230; it&#8217;s still not OK. I don&#8217;t want the changes as fast as the user can move the mouse. That&#8217;s too fast! My need for this subclass still stands, though the argument about the pixel-based notifications is null.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Suppose you want to throttle the rate at which a continuous NSSlider sends its action. To save you from the same fate I met, I will talk you through some of the avenues you might pursue, and ultimately describe the simple solution I adopted which is both easiest and is the only solution I know [&hellip;]<\/p>\n","protected":false},"author":10,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[],"class_list":["post-64","post","type-post","status-publish","format-standard","hentry","category-cocoa"],"_links":{"self":[{"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/posts\/64","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/users\/10"}],"replies":[{"embeddable":true,"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/comments?post=64"}],"version-history":[{"count":0,"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/posts\/64\/revisions"}],"wp:attachment":[{"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/media?parent=64"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/categories?post=64"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/tags?post=64"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}