{"id":165,"date":"2006-07-26T21:35:51","date_gmt":"2006-07-27T04:35:51","guid":{"rendered":"http:\/\/www.red-sweater.com\/blog\/165\/a-table-view-for-the-ages"},"modified":"2006-07-26T21:38:27","modified_gmt":"2006-07-27T04:38:27","slug":"a-table-view-for-the-ages","status":"publish","type":"post","link":"https:\/\/redsweater.com\/blog\/165\/a-table-view-for-the-ages","title":{"rendered":"A Table View For The Ages"},"content":{"rendered":"<p>Cocoa contains an awesome but sort of half-baked infrastructure for &#8220;autosaving&#8221; UI configurations for the user. Many of the common UI elements, such as windows, table views, and toolbars possess the ability to write out their configuration to the app&#8217;s preferences so they can be automatically restored the next time the app is launched.<\/p>\n<p>\nWhen this works, it works. And it is, as I said, awesome. But for such a cool idea there is a great lack of consistency in its implementation. NSWindow lets you autosave the frame by setting a unique name under which it will be saved. The API is rather extensive:\n<\/p>\n<p><pre>\n+ removeFrameUsingName:  \n&#8211; saveFrameUsingName:  \n&#8211; setFrameUsingName:  \n&#8211; setFrameUsingName:force:  \n&#8211; setFrameAutosaveName:  \n&#8211; frameAutosaveName  \n&#8211; setFrameFromString:  \n&#8211; stringWithSavedFrame \n<\/pre>\n<\/p>\n<p>\nNotice that there is no BOOL &#8220;autosavesFrame&#8221; method &#8211; it just figures if you set a name, you want it to be saved. This name can even be set from Interface Builder. The number of methods is a bit large for what seems like a simple operation, but what&#8217;s really nice is it exposes the &#8220;magic format&#8221; to  developers so we can override the default behavior or make different use of the autosave information.\n<\/p>\n<p>\nFor example, let&#8217;s say you wanted to implement a funky little &#8220;Expos&eacute;&#8221; feature for just your application, something that tiles all the windows so that they fit neatly into the available screen real estate. That&#8217;s fine, but you should be prepared to put all the windows back the way the user had them before. Using &#8220;stringWithSavedFrame,&#8221; this is trivial. Just iterate over your open windows and collect the screen position information in a format that <em>NSWindow itself<\/em> guarantees to be usable for resetting it.  It&#8217;s not like we couldn&#8217;t figure it out ourselves, but AppKit already figuerd it out! Thanks for sharing.\n<\/p>\n<p>\nNSToolbar takes a different approach. Instead of allowing developers to participate in the complicated question of &#8220;naming&#8221; the autosaved information, it offers a simpler API, implying it will come up with a way of saving the information itself:\n<\/p>\n<p><pre>\n&#8211; autosavesConfiguration  \n&#8211; setAutosavesConfiguration:  \n&#8211; configurationDictionary  \n&#8211; setConfigurationFromDictionary: \n<\/pre>\n<\/p>\n<p>\nThis is a great compromise. We&#8217;ve lost the ability to influence naming conventions, but who cares? They still give us access to the magic format! So if we don&#8217;t like the way it&#8217;s handling autosave, we just turn it off and implement our own with the help of &#8220;configurationDictionary.&#8221;\n<\/p>\n<p>\nBut NSTableView, oh NSTableView. You had to go your own way:\n<\/p>\n<p><pre>\n&#8211; autosaveName  \n&#8211; setAutosaveName:  \n&#8211; autosaveTableColumns  \n&#8211; setAutosaveTableColumns:  \n<\/pre>\n<\/p>\n<p>\nNSTableView lets us alter the autosave name, but unlike NSWindow, merely setting the name doesn&#8217;t imply that the feature is active. We have to set that BOOL separately. Notice how NSToolbar&#8217;s technique uses the same number of methods, but provides a lot more flexibility. The worst part about NSTableView&#8217;s approach is it <em>hides the magic data<\/em> so we can&#8217;t even override the default mechanism.\n<\/p>\n<p>\nIf only NSTableView exposed its magic data!\n<\/p>\n<p>\nI really wanted this functionality, because in developing <a href=\"http:\/\/www.red-sweater.com\/flextime\/\">FlexTime<\/a>, I decided to offer a feature to &#8220;save window layout&#8221; to the document itself. The other autosaving mechanisms work well with this approach &#8211; just grab the magic data and archive it. NSTableView stymies me, though! Perhaps I could set an autosaveName, then do something to provoke it being saved, then look it up manually. Nah! It&#8217;s all too fragile.\n<\/p>\n<p>\n<a href=\"http:\/\/www.red-sweater.com\/blog\/downloads\/NSTableView+RSAutosaving.zip\">NSTableView+RSAutosaving<\/a> is my solution. Yours too, under MIT License. It&#8217;s a category on NSTableView that adds the missing (IMHO) methods:\n<\/p>\n<p><pre>\n- dictionaryForAutosavingLayout\n- adjustLayoutForAutosavedDictionary:\n<\/pre>\n<\/p>\n<p>\nThe &#8220;magic data&#8221; in this case consists of the widths of every column and their ordering in the table. There are a few subtle gotchas to doing this right, and I think I did it right. So you&#8217;ll be glad if you end up not writing it yourself.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Cocoa contains an awesome but sort of half-baked infrastructure for &#8220;autosaving&#8221; UI configurations for the user. Many of the common UI elements, such as windows, table views, and toolbars possess the ability to write out their configuration to the app&#8217;s preferences so they can be automatically restored the next time the app is launched. When [&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,29,20,15],"tags":[],"class_list":["post-165","post","type-post","status-publish","format-standard","hentry","category-cocoa","category-free-code","category-hacking","category-programming"],"_links":{"self":[{"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/posts\/165","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=165"}],"version-history":[{"count":0,"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/posts\/165\/revisions"}],"wp:attachment":[{"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/media?parent=165"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/categories?post=165"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/redsweater.com\/blog\/wp-json\/wp\/v2\/tags?post=165"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}