<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:media="http://search.yahoo.com/mrss/"
>

<channel>
	<title>SwiftUI &#8211; Wade Tregaskis</title>
	<atom:link href="https://wadetregaskis.com/tags/swiftui/feed/" rel="self" type="application/rss+xml" />
	<link>https://wadetregaskis.com</link>
	<description></description>
	<lastBuildDate>Tue, 24 Sep 2024 18:03:21 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://wadetregaskis.com/wp-content/uploads/2016/03/Stitch-512x512-1-256x256.png</url>
	<title>SwiftUI &#8211; Wade Tregaskis</title>
	<link>https://wadetregaskis.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">226351702</site>	<item>
		<title>presentedWindowStyle is not windowStyle</title>
		<link>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/</link>
					<comments>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 23 Sep 2024 23:12:29 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Feedback Assistant]]></category>
		<category><![CDATA[Happy]]></category>
		<category><![CDATA[presentedWindowStyle]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[windowStyle]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8460</guid>

					<description><![CDATA[This post is mostly to herald a pretty good Apple bug report response, which as we know is a too-rare event. But it might also help others with this confusing SwiftUI API. What&#8217;s the difference between presentedWindowStyle(_:) and windowStyle(_:)? Well, one does something, the other doesn&#8217;t, basically. I tried using the former, and observed that&#8230; <a class="read-more-link" href="https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This post is mostly to herald a pretty good Apple bug report response, which as we know is a too-rare event.  But it might also help others with this confusing SwiftUI API.</p>



<p>What&#8217;s the difference between <code><a href="https://developer.apple.com/documentation/swiftui/view/presentedwindowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener">presentedWindowStyle(_:)</a></code> and <code><a href="https://developer.apple.com/documentation/swiftui/scene/windowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener">windowStyle(_:)</a></code>?</p>



<p>Well, one does something, the other doesn&#8217;t, basically.</p>



<p>I tried using the former, and observed that it never has any effect.  I filed FB14892608 about it, a month ago.  While the long delay isn&#8217;t great, the response I got today was actually pretty helpful:</p>



<pre class="wp-block-preformatted">We’re sorry you ran into trouble with that API.<br><br>To adjust the style of a window group’s windows, you have a couple of options. If all of the windows in the group should have the same style, then using the windowStyle() scene modifier is what you want:<br><br>struct MyApp: App {<br>    var body: some Scene {<br>        WindowGroup {<br>            ContentView()<br>        }<br>        .windowStyle(.hiddenTitleBar)<br>    }<br>}<br><br>If you need to adjust the style on an individual basis using state for that window, there are also some related view modifiers:<br>struct MyApp: App {<br>    var body: some Scene {<br>        WindowGroup {<br>            ContentView()<br>                .toolbar(removing: .title)<br>                .toolbarBackgroundVisibility(.hidden, for: .windowToolbar)<br>        }<br>    }<br>}</pre>



<p>A simple apology followed by clear and specific instructions on how to actually achieve what I wanted.  I genuinely applaud Apple &#8211; or perhaps I should more specifically thank the individual(s) within DTS that actually provided this response.  Either way, it was a very pleasant surprise.</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<p>Now, it still leaves unanswered the question of what <code>presentedWindowStyle(_:)</code> is supposed to be for, and in what situations it actually <em>does</em> anything.  So not a perfect reply, per se, but really the proper solution for that is either:</p>



<ol style="list-style-type:upper-alpha" class="wp-block-list">
<li>Fix the API to not have such confusingly similar modifiers.</li>



<li>Improve the documentation to:
<ul class="wp-block-list">
<li>Explicitly point out the other, similarly-named modifier.</li>



<li>Distinguish them.</li>
</ul>
</li>
</ol>



<p>Currently the documentation is just:</p>



<figure class="wp-block-pullquote"><blockquote><p>Sets the style for windows created by this scene.</p><cite><a href="https://developer.apple.com/documentation/swiftui/scene/windowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener"><code>windowStyle(_:)</code> documentation</a></cite></blockquote></figure>



<p>Versus:</p>



<figure class="wp-block-pullquote"><blockquote><p>Sets the style for windows created by interacting with this view.</p><cite><a href="https://developer.apple.com/documentation/swiftui/view/presentedwindowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener"><code>presentedWindowStyle(_:)</code> documentation</a></cite></blockquote></figure>



<p>The latter might <em>technically</em> convey something important here, in the <em>created by interacting with this view</em> part, but so much in SwiftUI is modifiers stuck haphazardly in weird and arbitrary places, that I&#8217;m been conditioned to ignore the fact that I&#8217;m often applying modifiers to views that don&#8217;t actually affect those views.  And for all I know in SwiftUI parlance views <em>do</em> &#8220;create&#8221; and/or &#8220;interact&#8221; with their parent windows (a <em>lot</em> about SwiftUI is backwards, part of its nature as a declarative API).</p>



<p>Especially if that&#8217;s the first candidate API you stumble across, when searching for a way to style a window, it&#8217;s very easy to presume it&#8217;s the right API.  Why would there be <em>multiple</em> APIs for styling the window; why would you continue searching after finding one?</p>



<p>Similarly, the <code>presentedWindowStyle(_:)</code> variant is the <em>only</em> one that appears in Xcode&#8217;s auto-complete if you try to add the modifier to your main view, which is both where you sometimes <em>have</em> to add window-level modifiers and also just a really easy mistake to make (instead of adding the modifier to the <code><a href="https://developer.apple.com/documentation/swiftui/windowgroup" data-wpel-link="external" target="_blank" rel="external noopener">WindowGroup</a></code>, one indentation level up).</p>



<p>Lastly, it doesn&#8217;t help that I can&#8217;t find <em>any</em> situation in which <code>presentedWindowStyle(_:)</code> has any effect, even knowing it&#8217;s not intended to style the parent window. One might assume it&#8217;s somehow related to sheets or somesuch, but apparently not? Presumably I&#8217;m overlooking some use-case &#8211; or maybe it <em>doesn&#8217;t</em> actually do anything on macOS, only iDevices? I welcome clues or tips.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8460</post-id>	</item>
		<item>
		<title>&#8220;Import from iPhone or iPad&#8221; doesn&#8217;t work when any view contains a SwiftUI Toggle</title>
		<link>https://wadetregaskis.com/import-from-iphone-or-ipad-doesnt-work-when-any-view-contains-a-swiftui-toggle/</link>
					<comments>https://wadetregaskis.com/import-from-iphone-or-ipad-doesnt-work-when-any-view-contains-a-swiftui-toggle/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 22 Aug 2024 23:22:42 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Continuity Camera]]></category>
		<category><![CDATA[Import from iPhone or iPad]]></category>
		<category><![CDATA[importableFromServices]]></category>
		<category><![CDATA[ImportFromDevicesCommands]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Toggle]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8371</guid>

					<description><![CDATA[This is a public reposting of FB14893699, in case it’s helpful to anyone else or especially in case someone else has seen this too and knows how to work around it. If any view in the [active] window contains a Toggle &#8211; even one that’s disabled or hidden &#8211; then Continuity Camera (re. ImportFromDevicesCommands and&#8230; <a class="read-more-link" href="https://wadetregaskis.com/import-from-iphone-or-ipad-doesnt-work-when-any-view-contains-a-swiftui-toggle/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>This is a public reposting of FB14893699, in case it’s helpful to anyone else or especially in case someone else has seen this too and knows how to work around it.</p>
</div></div>



<p>If <em>any</em> view in the [active] window contains a <a href="https://developer.apple.com/documentation/swiftui/toggle" data-wpel-link="external" target="_blank" rel="external noopener"><code>Toggle</code></a> &#8211; even one that’s disabled or hidden &#8211; then Continuity Camera (re. <code><a href="http://ImportFromDevicesCommands" data-wpel-link="external" target="_blank" rel="external noopener">ImportFromDevicesCommands</a></code> and <code><a href="https://developer.apple.com/documentation/scenekit/sceneview/4049460-importablefromservices" data-wpel-link="external" target="_blank" rel="external noopener">importableFromServices</a></code>) doesn’t work; all the submenu items under “Import from iPhone or iPad” are disabled.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img fetchpriority="high" decoding="async" width="412" height="309" src="https://wadetregaskis.com/wp-content/uploads/2024/08/22import-from-iphone-or-ipad22-disabled.webp" alt="Screenshot of the File menu with the &quot;Import from iPhone or iPad&quot; submenu expanded, and all items therein are disabled." class="wp-image-8372" srcset="https://wadetregaskis.com/wp-content/uploads/2024/08/22import-from-iphone-or-ipad22-disabled.webp 412w, https://wadetregaskis.com/wp-content/uploads/2024/08/22import-from-iphone-or-ipad22-disabled-256x192.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/08/22import-from-iphone-or-ipad22-disabled@2x.webp 824w" sizes="(max-width: 412px) 100vw, 412px" /></figure>
</div>


<p>I don’t know if this is truly specific to <code>Toggle</code>, that’s just the example case I happen to have isolated [first?].</p>



<p>What’s really weird is that once a <code>Toggle</code> has <em>ever</em> been displayed, even if you subsequently remove it from the view hierarchy entirely the “Import from iPhone or iPad” submenu items all remain disabled.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #AF00DB">import</span><span style="color: #000000"> </span><span style="color: #267F99">SwiftUI</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">@main</span></span>
<span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">Example</span><span style="color: #000000">: App {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@State</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> breakImportFromiDevice = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@State</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> text = </span><span style="color: #A31515">&quot;&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: some Scene {</span></span>
<span class="line"><span style="color: #000000">        WindowGroup {</span></span>
<span class="line"><span style="color: #000000">            VStack {</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #795E26">TextField</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Input&quot;</span><span style="color: #000000">, </span><span style="color: #795E26">text</span><span style="color: #000000">: $text) </span><span style="color: #008000">// Doesn&#39;t break anything.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #AF00DB">if</span><span style="color: #000000"> breakImportFromiDevice {</span></span>
<span class="line"><span style="color: #000000">                    </span><span style="color: #795E26">Toggle</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Break import from iDevice&quot;</span><span style="color: #000000">, </span><span style="color: #795E26">isOn</span><span style="color: #000000">: $breakImportFromiDevice)</span></span>
<span class="line"><span style="color: #000000">                }</span></span>
<span class="line"><span style="color: #000000">            }</span></span>
<span class="line"><span style="color: #000000">            .</span><span style="color: #795E26">importableFromServices</span><span style="color: #000000">(</span><span style="color: #795E26">action</span><span style="color: #000000">: { (</span><span style="color: #795E26">images</span><span style="color: #000000">: [NSImage]) -&gt; </span><span style="color: #267F99">Bool</span><span style="color: #000000"> </span><span style="color: #AF00DB">in</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Load image!&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">            })</span></span>
<span class="line"><span style="color: #000000">        }.</span><span style="color: #001080">commands</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #795E26">ImportFromDevicesCommands</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span></code></pre></div>



<p>There are some circumstances in which this gets “unbroken” during interactions with other views and so forth, which is 100% reproducible in my real app but I have no idea what the reason is.  The steps involved in my real app are kinda ridiculous (and not in any way remotely a viable workaround) and make absolutely no sense &#8211; it only “unbreaks” when a specific view is in a specific weird state (itself kind of the result of a bug, albeit a benign one).  And that weird state is merely whether it’s displaying one image or another &#8211; which as far as SwiftUI is concerned is not even a difference in view state, since I’m just swapping <code><a href="https://developer.apple.com/documentation/appkit/nsimage" data-wpel-link="external" target="_blank" rel="external noopener">NSImage</a></code>s under the cover.</p>



<p>I figured it must be something to do with view focus, but after much experimentation I believe I can conclusively rule that out.  No matter which view has focus, or how focus is configured, or which views are even focusable at all, the problem persists.  Likewise for window focus and key state.  And, [accessability-]focusable views other than <code>Toggle</code> &#8211; e.g. <code><a href="https://developer.apple.com/documentation/swiftui/textfield" data-wpel-link="external" target="_blank" rel="external noopener">TextField</a></code> &#8211; don’t cause any issues.</p>



<p>Frankly it&#8217;s baffling, and a mite infuriating.  I can&#8217;t even conceive of how SwiftUI can be so incredibly broken regarding such basic functionality, and the apparent interaction of GUI elements that have absolutely no business together.</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<p>Tangentially, a few things I&#8217;ve noticed about this Continuity Camera feature:</p>



<ul class="wp-block-list">
<li>&#8220;Add Sketch&#8221; doesn&#8217;t do anything.  Unlike the other options, which open the camera app on the target iDevice, it has no effect. 🤷‍♂️</li>



<li>Within the Finder, you can right-click empty whitespace within a folder, and the contextual menu has this &#8220;Import from iPhone or iPad&#8221; option at the bottom.  That&#8217;s pretty handy &#8211; until now I&#8217;d been doing it the &#8220;hard&#8221; way by taking a photo on my iPhone and AirDropping it across to my Mac.<br><br>I&#8217;d never noticed that feature prior to debugging this problem (I wanted to confirm that multiple other apps worked just fine; that it wasn&#8217;t an issue with Continuity Camera system-wide).</li>



<li>TextEdit has the same feature but tweaks the wording to &#8220;Insert…&#8221; rather than &#8220;Import…&#8221;, which I thought is both a nice touch and frustratingly not something you can do in your own apps (at least, not in SwiftUI). 😕</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/import-from-iphone-or-ipad-doesnt-work-when-any-view-contains-a-swiftui-toggle/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/08/22import-from-iphone-or-ipad22-disabled.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8371</post-id>	</item>
		<item>
		<title>NSPasteboard crashes due to unsafe, internal concurrent memory mutation when handling file promises</title>
		<link>https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/</link>
					<comments>https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 22 Aug 2024 05:01:02 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[AppKit]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Drag & drop]]></category>
		<category><![CDATA[memory corruption]]></category>
		<category><![CDATA[NSItemProvider]]></category>
		<category><![CDATA[NSPasteboard]]></category>
		<category><![CDATA[NSPasteboardItem]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8369</guid>

					<description><![CDATA[This is a public reposting of FB14885505, in case it&#8217;s helpful to anyone else or especially in case someone else has seen this too and knows how to work around it. NSPasteboard mutates itself simultaneously from the main thread and the global concurrent Dispatch pool, w.r.t. to its internal type cache. This is surprisingly trivial&#8230; <a class="read-more-link" href="https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>This is a public reposting of FB14885505, in case it&#8217;s helpful to anyone else or especially in case someone else has seen this too and knows how to work around it.</p>
</div></div>



<p><code><a href="https://developer.apple.com/documentation/appkit/nspasteboard" data-wpel-link="external" target="_blank" rel="external noopener">NSPasteboard</a></code> mutates itself simultaneously from the main thread and the global concurrent <a href="https://developer.apple.com/documentation/DISPATCH" data-wpel-link="external" target="_blank" rel="external noopener">Dispatch</a> pool, w.r.t. to its internal type cache. This is surprisingly trivial to reproduce (sample code below) by just dropping, e.g. a file promise (such as by opening a PNG in Preview, revealing the thumbnails sidebar, and then dragging the thumbnail onto the sample project’s window).</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #AF00DB">import</span><span style="color: #000000"> </span><span style="color: #267F99">SwiftUI</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">ContentView</span><span style="color: #000000">: View {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: some View {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">Rectangle</span><span style="color: #000000">().</span><span style="color: #795E26">onDrop</span><span style="color: #000000">(</span><span style="color: #795E26">of</span><span style="color: #000000">: NSImage.</span><span style="color: #001080">imageTypes</span><span style="color: #000000">, </span><span style="color: #795E26">isTargeted</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">) { </span><span style="color: #001080">_</span><span style="color: #000000"> </span><span style="color: #AF00DB">in</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> pb = </span><span style="color: #795E26">NSPasteboard</span><span style="color: #000000">(</span><span style="color: #795E26">name</span><span style="color: #000000">: .</span><span style="color: #001080">drag</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #001080">_</span><span style="color: #000000"> = pb.</span><span style="color: #001080">pasteboardItems</span><span style="color: #000000"> </span><span style="color: #008000">// Seems to be necessary for the crash.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #001080">_</span><span style="color: #000000"> = NSImage.</span><span style="color: #001080">imageTypes</span><span style="color: #000000"> </span><span style="color: #008000">// Not strictly necessary for the crash, but seems to make it more likely. 🤷‍♂️</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Judging from the callstack that runs in the concurrent pool, this is specific to file promises (and that seems to match my experience &#8211; it only crashes for some test cases, all of which involve file promises being present in the drag pasteboard at the time of the drop).</p>



<p>Since this bug causes semi-random memory corruption, it manifests in a large number of ways &#8211; not all of which are all that helpful.  But at least one case I’ve seen a few times is helpful, as it clearly shows the offending internal <code>NSPasteboard</code> code running concurrently with itself, e.g.:</p>



<pre class="wp-block-preformatted">Main queue / thread:
#0	0x00007ff80108dab5 in _platform_bzero$VARIANT$Haswell ()
#1	0x000000010df8774d in GuardMalloc_mallocInternal ()
#2	0x00007ff90792542f in stack_logging_lite_malloc ()
#3	0x00007ff800ea8733 in _malloc_zone_malloc_instrumented_or_legacy ()
#4	0x00007ff800f39a72 in _vasprintf ()
#5	0x00007ff800f16922 in asprintf ()
#6	0x00007ff80125655a in -[NSObject(NSObject) __dealloc_zombie] ()
#7	0x00007ff8020e039a in -[NSConcreteMapTable dealloc] ()
#8	0x00007ff804876983 in -[NSPasteboard _updateTypeCacheIfNeeded] ()
#9	0x00007ff8048763df in -[NSPasteboard _typesAtIndex:combinesItems:] ()
#10	0x00007ff804aad148 in NSCoreDragReceiveMessageProc ()
#11	0x00007ff807517b1a in CallReceiveMessageCollectionWithMessage ()
#12	0x00007ff8075124fa in DoMultipartDropMessage ()
#13	0x00007ff8075122ce in DoDropMessage ()
#14	0x00007ff8075159a9 in CoreDragMessageHandler ()
#15	0x00007ff8011d776b in __CFMessagePortPerform ()
#16	0x00007ff80113e5b7 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ ()
#17	0x00007ff80113e4ee in __CFRunLoopDoSource1 ()
#18	0x00007ff80113d166 in __CFRunLoopRun ()
#19	0x00007ff80113c112 in CFRunLoopRunSpecific ()
#20	0x00007ff80bb55a09 in RunCurrentEventLoopInMode ()
#21	0x00007ff80bb55646 in ReceiveNextEventCommon ()
#22	0x00007ff80bb55561 in _BlockUntilNextEventMatchingListInModeWithFilter ()
#23	0x00007ff8047acc61 in _DPSNextEvent ()
#24	0x00007ff8050c0dc0 in -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] ()
#25	0x00007ff80479e075 in -[NSApplication run] ()
#26	0x00007ff804771ff3 in NSApplicationMain ()
#27	0x00007ff90dc24557 in ___lldb_unnamed_symbol57096 ()
#28	0x00007ff90e31fe64 in ___lldb_unnamed_symbol104448 ()
#29	0x00007ff90e6e63ff in static SwiftUI.App.main() -&gt; () ()
#30	0x000000010dfa5cce in static NSPasteboardItem_CrashApp.$main() ()
#31	0x000000010dfa5d69 in main at /Users/SadPanda/Documents/NSPasteboardItem Crash/NSPasteboardItem Crash/NSPasteboardItem_CrashApp.swift:11
#32	0x00007ff800cd5366 in start ()

Dispatch concurrent queue (default QoS):
#0	0x00007ff80111b45c in -[__NSSetM addObject:] ()
#1	0x00007ff80487692e in -[NSPasteboard _updateTypeCacheIfNeeded] ()
#2	0x00007ff8048763df in -[NSPasteboard _typesAtIndex:combinesItems:] ()
#3	0x00007ff804aa9597 in -[NSPasteboard _canRequestDataForType:index:usesPboardTypes:combinesItems:] ()
#4	0x00007ff804fdd161 in -[NSPasteboard _dataForType:index:usesPboardTypes:combinesItems:securityScoped:] ()
#5	0x00007ff804aa7c4b in -[NSPasteboardItem dataForType:] ()
#6	0x00007ff8055804af in -[NSFilePromiseReceiver receivePromisedFilesAtDestination:options:operationQueue:reader:] ()
#7	0x00007ff90e787b51 in ___lldb_unnamed_symbol131674 ()
#8	0x00007ff90e9bc340 in ___lldb_unnamed_symbol148147 ()
#9	0x00007ff8020d00ba in __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ ()
#10	0x00007ff8020cffb8 in -[NSBlockOperation main] ()
#11	0x00007ff8020cff4b in __NSOPERATION_IS_INVOKING_MAIN__ ()
#12	0x00007ff8020cf1ec in -[NSOperation start] ()
#13	0x00007ff8020cef0d in __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ ()
#14	0x00007ff8020cedde in __NSOQSchedule_f ()
#15	0x000000010e68ce7d in _dispatch_block_async_invoke2 ()
#16	0x000000010e67ca7b in _dispatch_client_callout ()
#17	0x000000010e67fa09 in _dispatch_continuation_pop ()
#18	0x000000010e67eae8 in _dispatch_async_redirect_invoke ()
#19	0x000000010e6906a9 in _dispatch_root_queue_drain ()
#20	0x000000010e6911ba in _dispatch_worker_thread2 ()
#21	0x000000010dfb832f in _pthread_wqthread ()
#22	0x000000010dfbebeb in start_wqthread ()</pre>



<p>There doesn’t appear to be any workaround (short of not supporting drops at all!).</p>



<p>The more complicated the drop handler the more likely it is to promptly crash upon drop &#8211; in my real code with a non-trivial handler, it’s virtually guaranteed to crash on the second drop containing a file promise, while in the vastly reduced sample code (above) it can take dozens of drops before it finally crashes outright.</p>



<p>I have not <em>directly</em> tested whether this <code>NSPasteboard</code> bug occurs in the absence of <a href="https://developer.apple.com/documentation/SwiftUI" data-wpel-link="external" target="_blank" rel="external noopener">SwiftUI</a>, so I don’t strictly know if the root cause is in <a href="https://developer.apple.com/documentation/appkit" data-wpel-link="external" target="_blank" rel="external noopener">AppKit</a> or SwiftUI. However, since most SwiftUI apps don’t support drag-and-drop, but plenty of AppKit ones do and manage to not crash when given the exact same test cases, I do strongly suspect SwiftUI is causing this somehow.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ You may wonder why I’m directly accessing the drag pasteboard rather than using the <code><a href="https://developer.apple.com/documentation/foundation/nsitemprovider" data-wpel-link="external" target="_blank" rel="external noopener">NSItemProvider</a></code>s provided by SwiftUI. It’s because that API is horribly broken &#8211; in many cases the provided <code>NSItemProvider</code>(s) are duds that contain no actual data. So I have to use the drag pasteboard directly in order to stand any chance of supporting drag &amp; drop.</p>



<p>Also, the <code>NSItemProvider</code>-based API is harder to use and doesn&#8217;t support important aspects of drag-and-drop, like file promises (although, with <code>NSPasteboard</code> apparently corrupting itself when file promises are received, I guess none of Apple&#8217;s APIs do anymore 😔).</p>
</div></div>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<h3 class="wp-block-heading">Follow-up (September 12th, 2024)</h3>



<p>I actually received a response from Apple, from a real human (or at least a convincing AI).  Ultimately their response didn&#8217;t help as it contained some mistakes, but I&#8217;m hopeful there&#8217;ll be more follow-up and a productive conclusion.  And in the interim, they did assert a few things which are important to know, and are not otherwise documented by Apple:</p>



<ul class="wp-block-list">
<li>SwiftUI&#8217;s <code><a href="https://developer.apple.com/documentation/swiftui/view/ondrop(of:istargeted:perform:)-f15m" data-wpel-link="external" target="_blank" rel="external noopener">onDrop(of:isTargeted:perform:)</a></code> method makes no claims or promises as to what thread / queue it executes the closure on, and in fact according to the anonymous Apple engineer it <em>never</em> executes the closure on the main thread.<br><br>Now, while that may be the intent, the reality of that is wrong &#8211; in my experience it <em>always</em> executes the closure on the main thread (which makes a lot of sense to me as drag-and-drop event handling in AppKit has always been on the main thread in practice).<br><br>Nonetheless, Apple says one cannot rely on the current behaviour and should in fact assume it <em>never</em> executes on the main thread (though in practice that means you have to <em>check</em>, not assume, since if you blindly do something like <code>DispatchQueue.main.sync</code><code style="font-size: 15px;"> { … }</code> in your drop handler your code <em>will</em> deadlock, today).</li>



<li><code><a href="https://developer.apple.com/documentation/appkit/nspasteboard" data-wpel-link="external" target="_blank" rel="external noopener">NSPasteboard</a></code> is not safe to use outside the main thread / queue.<br><br>This isn&#8217;t documented anywhere public &#8211; not in <code>NSPasteboard</code>&#8216;s documentation itself, nor the ancient <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB" data-wpel-link="external" target="_blank" rel="external noopener">Application Kit Framework Thread Safety</a> documentation.<br><br>I wouldn&#8217;t be surprised if it&#8217;s broadly true, as the AppKit APIs involving it always seemed main-thread centric anyway (all the handlers and delegate methods involving pasteboards are invoked on the main thread, in my experience).  And it&#8217;s generally best to assume everything in AppKit is main-thread-only unless it&#8217;s explicitly documented otherwise.<br><br>However, it&#8217;s important to note that Apple&#8217;s <em>own</em> code doesn&#8217;t follow this rule &#8211; e.g. <code><a href="https://developer.apple.com/documentation/appkit/nsfilepromisereceiver" data-wpel-link="external" target="_blank" rel="external noopener">NSFilePromiseReceiver</a></code>, internally, uses <code>NSPasteboard</code> from the global concurrent queue.</li>
</ul>



<p>Even though Apple&#8217;s initial response to this bug report hasn&#8217;t been all that fruitful, I do want to emphasise the fact that they <em>did</em> respond, which was a pleasant surprise and very much appreciated.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8369</post-id>	</item>
		<item>
		<title>Including Services in contextual menus in SwiftUI</title>
		<link>https://wadetregaskis.com/including-services-in-contextual-menus-in-swiftui/</link>
					<comments>https://wadetregaskis.com/including-services-in-contextual-menus-in-swiftui/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 19 Mar 2024 01:35:39 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[AppKit]]></category>
		<category><![CDATA[Contextual Menus]]></category>
		<category><![CDATA[Electron]]></category>
		<category><![CDATA[NSHostingView]]></category>
		<category><![CDATA[NSMenu]]></category>
		<category><![CDATA[NSMenuItem]]></category>
		<category><![CDATA[NSServicesMenuRequestor]]></category>
		<category><![CDATA[NSViewRepresentable]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7861</guid>

					<description><![CDATA[SwiftUI provides a way to provide a contextual menu for a view, with contextMenu(menuItems:) and friends, but it requires you to manually specify the entire contents of the contextual menu. That means it does not include the standard Services submenu. A brief history of Contextual Menus Contextual menus were introduced [to the Mac] in 1997&#8230; <a class="read-more-link" href="https://wadetregaskis.com/including-services-in-contextual-menus-in-swiftui/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>SwiftUI provides a way to provide a contextual menu for a view, with <code><a href="https://developer.apple.com/documentation/swiftui/view/contextmenu(menuitems:)" data-wpel-link="external" target="_blank" rel="external noopener">contextMenu(menuItems:)</a></code> and friends, but it requires you to manually specify the <em>entire</em> contents of the contextual menu.  That means it does not include the standard Services submenu.</p>



<h2 class="wp-block-heading">A brief history of Contextual Menus</h2>



<p>Contextual menus were introduced [to the Mac] in 1997 with <a href="https://infinitemac.org/1997/Mac%20OS%208.0" data-wpel-link="external" target="_blank" rel="external noopener">Mac OS 8</a>, with the new Contextual Menu Manager system extension and associated <a href="http://preserve.mactech.com/articles/mactech/Vol.14/14.02/ContextualMenuModules/index.html" data-wpel-link="external" target="_blank" rel="external noopener">Contextual Menu Modules</a>.  See also <a href="https://preterhuman.net/macstuff/techpubs/mac/pdf/HIGOS8Guidelines.pdf" data-wpel-link="external" target="_blank" rel="external noopener">the Mac OS 8 edition of the HIG</a> (page 93).</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="354" height="436" src="https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-Finder-file-contextual-menu.webp" alt="" class="wp-image-7862" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-Finder-file-contextual-menu.webp 354w, https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-Finder-file-contextual-menu-208x256.webp 208w" sizes="(max-width: 354px) 100vw, 354px" /></figure>
</div>


<p>Support varied a lot in the early days, though, and some user-interface conventions didn&#8217;t solidify until a few years later.  e.g. SimpleText didn&#8217;t support contextual menus at all in Mac OS 8.0, and while 3rd party apps like BBEdit eventually did, it took longer still for now-standard items to become commonplace, like Cut / Copy / Paste.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="820" height="404" src="https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-BBEdit-5-1-1-contextual-menu.webp" alt="" class="wp-image-7863" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-BBEdit-5-1-1-contextual-menu.webp 820w, https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-BBEdit-5-1-1-contextual-menu-256x126.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-BBEdit-5-1-1-contextual-menu-768x378.webp 768w, https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-BBEdit-5-1-1-contextual-menu-256x126@2x.webp 512w" sizes="(max-width: 820px) 100vw, 820px" /></figure>
</div>


<p>Broadly-speaking, contextual menus have changed very little over the decades.  At some point the app-specific commands were separated from the system-wide commands, the latter becoming relegated to the current &#8220;Services&#8221; submenu.  And the specific menu items have of course evolved over time, but the basic idea has persisted: to provide app-specific or built-in commands followed by commands proffered by system-wide extensions.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="728" height="656" src="https://wadetregaskis.com/wp-content/uploads/2024/03/macOS-14-3-1-Sonoma-TextEdit-contextual-menu.webp" alt="Screenshot from macOS 14.3.1 Sonoma showing an untitled TextEdit window containing the text &quot;Old Macs never die, they just fade away&quot;. &quot;Fade&quot; is selected, and a contextual menu is popped up from it. The Services menu item is highlighted, with the submenu also open, listing various Apple and 3rd party service extensions." class="wp-image-7864" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/macOS-14-3-1-Sonoma-TextEdit-contextual-menu.webp 728w, https://wadetregaskis.com/wp-content/uploads/2024/03/macOS-14-3-1-Sonoma-TextEdit-contextual-menu-256x231.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/03/macOS-14-3-1-Sonoma-TextEdit-contextual-menu@2x.webp 1456w, https://wadetregaskis.com/wp-content/uploads/2024/03/macOS-14-3-1-Sonoma-TextEdit-contextual-menu-256x231@2x.webp 512w" sizes="auto, (max-width: 728px) 100vw, 728px" /></figure>
</div>


<p>It&#8217;s chuckle-worthy to remember that when Mac OS 8, with Contextual Menus, was introduced, Macs still had one-button mice.  I don&#8217;t recall but assume it was <em>possible</em> to connect a mouse with multiple buttons, to a Mac, but remember that no Macs had USB at this time (that didn&#8217;t arrive until the iMac in August 1998, nearly a year later).  Prior to USB, Macs used different I/O ports than PCs (<a href="https://en.wikipedia.org/wiki/Apple_Desktop_Bus" data-wpel-link="external" target="_blank" rel="external noopener">ADB</a> vs <a href="https://en.wikipedia.org/wiki/PS/2_port" data-wpel-link="external" target="_blank" rel="external noopener">PS/2</a>, for the most part), so there were a lot fewer mouse (and keyboard) options for Macs.  Thus, contextual menus were originally &#8211; and to this day still also &#8211; invoked by control-clicking.  You might have encountered the term &#8220;secondary-click[ing]&#8221;, which arose in that era to abstract over whether it was a physically distinct mouse button or just a modified left click<sup data-fn="0c0ed089-5fc2-434e-8cfb-699487865d79" class="fn"><a href="#0c0ed089-5fc2-434e-8cfb-699487865d79" id="0c0ed089-5fc2-434e-8cfb-699487865d79-link">1</a></sup>.</p>



<p>It <a href="https://www.macobserver.com/tips/how-to/how-to-right-click-on-a-mac/#:~:text=With%20the%20proliferation%20of%20Microsoft%20Windows%2C%20which%20embraced,with%20Mac%20OS%208%2C%20which%20debuted%20in%201997." data-wpel-link="external" target="_blank" rel="external noopener">apparently</a> wasn&#8217;t until the Mighty Mouse in 2005 that Apple actually shipped a multi-button mouse.  (I say apparently because I don&#8217;t personally recall, and I had switched to 3rd party mice long before then anyway so I probably didn&#8217;t even notice at the time)</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>🤔 I don&#8217;t know why SwiftUI gets the terminology wrong, calling it the &#8220;context menu&#8221; rather than contextual menu.  It&#8217;s not just SwiftUI &#8211; <a href="https://developer.apple.com/design/human-interface-guidelines/context-menus" data-wpel-link="external" target="_blank" rel="external noopener">Apple&#8217;s latest Human Interface Guidelines</a> have the same spelling error.</p>



<p>The correct term (in both English and from precedence) is <em>contextual</em>.  The menu provides <em>contextual</em> commands (or context-sensitive, if you prefer).  It is not a &#8220;context&#8221; menu.  That doesn&#8217;t even make sense.  It doesn&#8217;t provide context, it is contextual.</p>
</div></div>



<h2 class="wp-block-heading">Are Services still relevant?</h2>



<p>I assume that the Services submenu &#8211; whether through contextual menus or the app menu &#8211; is not used by most Mac users today, if only because the Mac user-base has expanded massively and most people barely leave their web browsers.  I&#8217;ve heard Services derided or overlooked as a &#8220;power-user&#8221; or &#8220;niche&#8221; feature.  Which is sad, because they can be very handy.</p>



<p>More importantly, some of your app&#8217;s users might rely heavily on Services as part of their personal workflow and choice, and it&#8217;s really not our place &#8211; as native Mac application developers &#8211; to tell them they shouldn&#8217;t use a standard system feature.</p>



<p>It&#8217;s frustrating for end-users to encounter applications which don&#8217;t support standard Mac features, like Services, and makes such applications stand out as non-native or otherwise broken.</p>



<p>It is sad that SwiftUI is in the general company of Electron and its ilk, here.</p>



<h2 class="wp-block-heading">So how&#8217;s it done?</h2>



<p>There&#8217;s <em>probably</em> at least two ways to do this, one of them being to manually insert the Services menu item into an otherwise vanilla SwiftUI menu.  But I quickly ran into non-trivial challenges in pursuing that avenue, as you can&#8217;t just ask AppKit for the Services menu item, or its contents.  Alas.</p>



<p>Ultimately I found it easier &#8211; and more in keeping with the grain &#8211; to instead just not use SwiftUI for contextual menus at all.  But fortunately that doesn&#8217;t mean abandoning SwiftUI entirely, merely intermingling some AppKit into it.</p>



<p>The basic design is the standard AppKit sandwich<sup data-fn="2d6f612a-7fb7-498e-bdd6-68f54de88f78" class="fn"><a href="#2d6f612a-7fb7-498e-bdd6-68f54de88f78" id="2d6f612a-7fb7-498e-bdd6-68f54de88f78-link">2</a></sup>:  an <code><a href="https://developer.apple.com/documentation/swiftui/nsviewrepresentable" data-wpel-link="external" target="_blank" rel="external noopener">NSViewRepresentable</a></code> containing an <code><a href="https://developer.apple.com/documentation/swiftui/nshostingview" data-wpel-link="external" target="_blank" rel="external noopener">NSHostingView</a></code>.</p>



<p>The first part is particularly easy, and just the usual annoying boilerplate:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">ContextualMenuView</span><span style="color: #000000">&lt;</span><span style="color: #0000FF">Content</span><span style="color: #000000">: </span><span style="color: #267F99">View</span><span style="color: #000000">&gt;: NSViewRepresentable {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> viewContent: () -&gt; Content</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> textProvider: </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> () -&gt; </span><span style="color: #267F99">String</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">init</span><span style="color: #000000">(@</span><span style="color: #795E26">ViewBuilder</span><span style="color: #000000"> </span><span style="color: #001080">viewContent</span><span style="color: #000000">: </span><span style="color: #0000FF">@escaping</span><span style="color: #000000"> () -&gt; Content,</span></span>
<span class="line"><span style="color: #000000">         </span><span style="color: #795E26">text</span><span style="color: #000000">: </span><span style="color: #0000FF">@autoclosure</span><span style="color: #000000"> </span><span style="color: #0000FF">@escaping</span><span style="color: #000000"> </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> () -&gt; </span><span style="color: #267F99">String</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">viewContent</span><span style="color: #000000"> = viewContent</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">textProvider</span><span style="color: #000000"> = text</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">updateNSView</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">nsView</span><span style="color: #000000">: ContextualMenuViewImplementation&lt;Content&gt;,</span></span>
<span class="line"><span style="color: #000000">                      </span><span style="color: #795E26">context</span><span style="color: #000000">: NSViewRepresentableContext&lt;ContextualMenuView&gt;) {</span></span>
<span class="line"><span style="color: #000000">        nsView.</span><span style="color: #001080">rootView</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">viewContent</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        nsView.</span><span style="color: #001080">textProvider</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">textProvider</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">makeNSView</span><span style="color: #000000">(</span><span style="color: #795E26">context</span><span style="color: #000000">: Context) -&gt; ContextualMenuViewImplementation&lt;Content&gt; {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">ContextualMenuViewImplementation</span><span style="color: #000000">(</span><span style="color: #795E26">rootView</span><span style="color: #000000">: </span><span style="color: #795E26">viewContent</span><span style="color: #000000">(),</span></span>
<span class="line"><span style="color: #000000">                                         </span><span style="color: #795E26">textProvider</span><span style="color: #000000">: textProvider)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">sizeThatFits</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">proposal</span><span style="color: #000000">: ProposedViewSize,</span></span>
<span class="line"><span style="color: #000000">                      </span><span style="color: #795E26">nsView</span><span style="color: #000000">: ContextualMenuViewImplementation&lt;Content&gt;,</span></span>
<span class="line"><span style="color: #000000">                      </span><span style="color: #795E26">context</span><span style="color: #000000">: Context) -&gt; CGSize? {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> nsView.</span><span style="color: #001080">fittingSize</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>I&#8217;ve hard-coded it for text in this example, for simplicity, but you can adjust that to suite your needs.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ The <code>sizeThatFits(…)</code> implementation is a bit arbitrary.  SwiftUI&#8217;s view sizing methodology is mildly infuriating, in the sense that it&#8217;s both very limited in its capabilities and very confusing for what little it does.  It took me a <em>lot</em> of trial-and-error and reverse engineering to figure out what value I needed to return.  But I suspect it&#8217;s context-sensitive, based on what the subview does.  So feel free to adjust it as necessary for your own use.</p>
</div></div>



<p>For convenience it&#8217;s nice to add a view modifier for this too:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">extension</span><span style="color: #000000"> </span><span style="color: #267F99">View</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">contextualMenu</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000"> </span><span style="color: #001080">textProvider</span><span style="color: #000000">: </span><span style="color: #0000FF">@autoclosure</span><span style="color: #000000"> </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> () -&gt; </span><span style="color: #267F99">String</span><span style="color: #000000">) -&gt; ContextualMenuView&lt;</span><span style="color: #0000FF">Self</span><span style="color: #000000">&gt; {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">ContextualMenuView</span><span style="color: #000000">(</span><span style="color: #795E26">viewContent</span><span style="color: #000000">: { </span><span style="color: #0000FF">self</span><span style="color: #000000"> },</span></span>
<span class="line"><span style="color: #000000">                           </span><span style="color: #795E26">text</span><span style="color: #000000">: textProvider)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>And now on to the real guts of all this, the custom <code>NSView</code> subclass that will define the contextual menu.  Fortunately, <code>NSView</code> has very straightforward built-in support for contextual menus, so you don&#8217;t need to worry about mouse-event handling &#8211; you just provide it a non-nil <code>NSMenu</code> and it does the rest.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">ContextualMenuViewImplementation</span><span style="color: #000000">&lt;</span><span style="color: #0000FF">Content</span><span style="color: #000000">: </span><span style="color: #267F99">View</span><span style="color: #000000">&gt;: NSHostingView&lt;Content&gt;,</span></span>
<span class="line"><span style="color: #000000">                                                       NSServicesMenuRequestor {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">fileprivate</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> textProvider: </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> () -&gt; </span><span style="color: #267F99">String</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">required</span><span style="color: #000000"> </span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">rootView</span><span style="color: #000000">: Content,</span></span>
<span class="line"><span style="color: #000000">                             </span><span style="color: #795E26">text</span><span style="color: #000000">: </span><span style="color: #0000FF">@autoclosure</span><span style="color: #000000"> </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> () -&gt; </span><span style="color: #267F99">String</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">textProvider</span><span style="color: #000000"> = text</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">rootView</span><span style="color: #000000">: rootView)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Mandated by NSHostingView, but not actually necessary for our purposes here.  But feel free to give this a real implementation, if that makes sense for your use and needs.</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">required</span><span style="color: #000000"> </span><span style="color: #0000FF">dynamic</span><span style="color: #000000"> </span><span style="color: #0000FF">init?</span><span style="color: #000000">(</span><span style="color: #795E26">coder</span><span style="color: #000000"> </span><span style="color: #001080">aDecoder</span><span style="color: #000000">: NSCoder) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">fatalError</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;init(coder:) has not been implemented for ContextualMenuView&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// As above.</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">required</span><span style="color: #000000"> </span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">rootView</span><span style="color: #000000">: Content) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">fatalError</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;init(rootView:) has not been implemented for ContextualMenuView&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">validRequestor</span><span style="color: #000000">(</span><span style="color: #795E26">forSendType</span><span style="color: #000000"> </span><span style="color: #001080">sendType</span><span style="color: #000000">: NSPasteboard.PasteboardType?,</span></span>
<span class="line"><span style="color: #000000">                                       </span><span style="color: #795E26">returnType</span><span style="color: #000000">: NSPasteboard.PasteboardType?) -&gt; </span><span style="color: #267F99">Any</span><span style="color: #000000">? {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">guard</span><span style="color: #000000"> sendType == .</span><span style="color: #001080">string</span><span style="color: #000000"> || sendType == .</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;NSStringPboardType&quot;</span><span style="color: #000000">) </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">validRequestor</span><span style="color: #000000">(</span><span style="color: #795E26">forSendType</span><span style="color: #000000">: sendType, </span><span style="color: #795E26">returnType</span><span style="color: #000000">: returnType)</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">self</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">nonisolated</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">writeSelection</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000"> </span><span style="color: #001080">pboard</span><span style="color: #000000">: NSPasteboard,</span></span>
<span class="line"><span style="color: #000000">                                          </span><span style="color: #795E26">types</span><span style="color: #000000">: [NSPasteboard.PasteboardType]) -&gt; </span><span style="color: #267F99">Bool</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">guard</span><span style="color: #000000"> types.</span><span style="color: #795E26">contains</span><span style="color: #000000">(.</span><span style="color: #001080">string</span><span style="color: #000000">) || types.</span><span style="color: #795E26">contains</span><span style="color: #000000">(.</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;NSStringPboardType&quot;</span><span style="color: #000000">)) </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">false</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">        </span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> text = </span><span style="color: #AF00DB">if</span><span style="color: #000000"> Thread.</span><span style="color: #001080">isMainThread</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        } </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #001080">sync</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">            }</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        pboard.</span><span style="color: #795E26">setString</span><span style="color: #000000">(text, </span><span style="color: #795E26">forType</span><span style="color: #000000">: .</span><span style="color: #001080">string</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">menu</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000"> </span><span style="color: #001080">event</span><span style="color: #000000">: NSEvent) -&gt; NSMenu? {</span></span>
<span class="line"><span style="color: #000000">        NSApplication.</span><span style="color: #001080">shared</span><span style="color: #000000">.</span><span style="color: #795E26">registerServicesMenuSendTypes</span><span style="color: #000000">([.</span><span style="color: #001080">string</span><span style="color: #000000">, .</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;NSStringPboardType&quot;</span><span style="color: #000000">)],</span></span>
<span class="line"><span style="color: #000000">                                                           </span><span style="color: #795E26">returnTypes</span><span style="color: #000000">: [])</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> menu = </span><span style="color: #795E26">NSMenu</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        menu.</span><span style="color: #001080">allowsContextMenuPlugIns</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// Insert other menu items here.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> menu</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ <code><a href="https://developer.apple.com/documentation/appkit/nsapplication/1428751-registerservicesmenusendtypes" data-wpel-link="external" target="_blank" rel="external noopener">registerServicesMenuSendTypes(_:returnTypes:)</a></code> is normally called in <code><a href="https://developer.apple.com/documentation/objectivec/nsobject/1418639-initialize" data-wpel-link="external" target="_blank" rel="external noopener">+initialize</a></code>, but Swift doesn&#8217;t provide any way to do that (it <em>explicitly</em> bans declaring the method on your <code>NSObject</code> subclasses, for no apparent reason &#8211; perhaps a limitation of Swift&#8217;s Objective-C interoperability).</p>



<p>So, in the example above I&#8217;ve called it (every time!) in <code><a href="https://developer.apple.com/documentation/appkit/nsview/1483231-menu" data-wpel-link="external" target="_blank" rel="external noopener">menu(for:)</a></code>.  That works, but it is inefficient &#8211; you only need to call it once per app session.  If your application has a natural, better place to put it, e.g. during app launch, move it there.</p>
</div></div>



<p>The only real complexity is in the <code><a href="https://developer.apple.com/documentation/appkit/nsservicesmenurequestor/" data-wpel-link="external" target="_blank" rel="external noopener">NSServicesMenuRequestor</a></code> delegate method <code><a href="https://developer.apple.com/documentation/appkit/nsservicesmenurequestor/1428477-writeselection" data-wpel-link="external" target="_blank" rel="external noopener">writeSelection(to:types:)</a></code>.  Because it&#8217;s declared <code>nonisolated</code> by the <code>NSServicesMenuRequestor</code> protocol, you&#8217;re forced to assume it can be called in <em>any</em> isolation context; from any thread.  Unfortunately, at runtime it&#8217;s sometimes &#8211; but not always! &#8211; called from the <em>main</em> thread.  Swift doesn&#8217;t have an elegant way to say &#8220;run this synchronously on the main thread / actor&#8221; &#8211; if you naively call <code><a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/" data-wpel-link="external" target="_blank" rel="external noopener">DispatchQueue</a>.<a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/1781006-main" data-wpel-link="external" target="_blank" rel="external noopener">main</a>.<a href="https://developer.apple.com/documentation/dispatch/dispatchqueue#3119600" data-wpel-link="external" target="_blank" rel="external noopener">sync(…)</a></code> and you&#8217;re <em>already</em> on the main queue, it crashes your application!  So you must manually check for being on the main thread, and handle that specially. 😒</p>



<p>The other thing you might want to consider, not shown in the simple example above, is whether you want to support Services sending data <em>back</em> to your view.  e.g. they might transform the text and return the new text to you, or generate / source text from somewhere else entirely for you.  If you wish to support that, you need to populate the <code>returnTypes</code> argument to <code><a href="https://developer.apple.com/documentation/appkit/nsapplication/1428751-registerservicesmenusendtypes" data-wpel-link="external" target="_blank" rel="external noopener">registerServicesMenuSendTypes(_:returnTypes:)</a></code> <em>and</em> implement the <code><a href="https://developer.apple.com/documentation/appkit/nsservicesmenurequestor/1428481-readselection" data-wpel-link="external" target="_blank" rel="external noopener">readSelection(from:)</a></code> delegate method.</p>



<h2 class="wp-block-heading">How&#8217;s it done <em>better</em>?</h2>



<p>The above works &#8211; and is the canonical way to do it.  It results in the same plain Services submenu as you&#8217;ll see throughout Mac apps.</p>



<p>But that menu&#8217;s not great.  It just dumps everything it a big amorphous list, with no delineation and merely alphabetical ordering.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="334" height="496" src="https://wadetregaskis.com/wp-content/uploads/2024/03/Dull-Services-submenu.webp" alt="Screenshot of the default Services menu for contextual menus, showing a plain list of items ordered alphabetically." class="wp-image-7866" style="width:334px;height:auto" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/Dull-Services-submenu.webp 334w, https://wadetregaskis.com/wp-content/uploads/2024/03/Dull-Services-submenu-172x256.webp 172w, https://wadetregaskis.com/wp-content/uploads/2024/03/Dull-Services-submenu@2x.webp 668w" sizes="auto, (max-width: 334px) 100vw, 334px" /></figure>
</div>


<p>Whereas if you look the Services submenu in the application menu:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="442" height="815" src="https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu.webp" alt="Screenshot of the good Services submenu (as found in the application menu), with more options, app icons, and better grouping." class="wp-image-7867" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu.webp 442w, https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu-139x256.webp 139w, https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu@2x.webp 884w, https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu-139x256@2x.webp 278w" sizes="auto, (max-width: 442px) 100vw, 442px" /></figure>
</div>


<p>…it is superior in many ways:</p>



<ul class="wp-block-list">
<li>There are more items.</li>



<li>There&#8217;s a link to System Preferences / Settings to adjust which services are shown.</li>



<li>There&#8217;s dividers in appropriate places, with subheadings, to help visually organise everything.</li>



<li>App icons are shown to better visually distinguish each service.</li>



<li>The grouping is somewhat alphabetical but with all services from a given app shown contiguously.</li>
</ul>



<p>Some of the items shown aren&#8217;t actually context-specific &#8211; or at least, to no more detail than merely which application is targeted &#8211; but they&#8217;re intentionally relegated to the bottom, and could be handy anyway.  e.g. if you can see any right-clickable area of any window of an application, you can quickly start profiling that application in Instruments.</p>



<p>It&#8217;s not <em>hard</em>, per se, to get the better menu.  But it&#8217;s not documented, not <em>officially</em> supported, and has some broken edge cases.  It <em>is</em> how <em>many</em> applications insert the Services menu into their menus (in fact it was <a href="https://github.com/electron/electron/blob/193e162ec6efbb688de2a0fd533c87ab0666d133/shell/browser/ui/cocoa/electron_menu_controller.mm#L332" data-wpel-link="external" target="_blank" rel="external noopener">the Electron source</a> which helped me figure out how to do this in the first place), so it&#8217;s highly unlikely Apple will break it in the foreseeable future.  Nonetheless, be warned.</p>



<p>The main edge case / bug that I&#8217;ve encountered is that this better Services submenu only works if the window is key.  And there&#8217;s no way to <em>force</em> the window to be key (all of the AppKit APIs which seem <em>specifically</em> for that purpose flat-out do not work, such as <code><a href="https://developer.apple.com/documentation/uikit/uiwindow/1621610-makekeywindow" data-wpel-link="external" target="_blank" rel="external noopener">makeKeyWindow</a></code>).  So you have to fall back to the lesser version of the Services menu in those cases.  Which is basically whenever the view&#8217;s window is not the active window when the right-click occurs (or, whenever that macOS bug hits whereby the window is <em>shown</em> as if it&#8217;s the key window but actually isn&#8217;t 😤).</p>



<p>Anyway, with all that in mind and apparently not having dissuaded you, here&#8217;s the code to go in <code>menu(for:)</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">menu.</span><span style="color: #001080">allowsContextMenuPlugIns</span><span style="color: #000000"> = !(</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">window</span><span style="color: #000000">?.</span><span style="color: #001080">isKeyWindow</span><span style="color: #000000"> ?? </span><span style="color: #0000FF">false</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> !menu.</span><span style="color: #001080">allowsContextMenuPlugIns</span><span style="color: #000000"> &amp;&amp; !(</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">window</span><span style="color: #000000">?.</span><span style="color: #795E26">makeFirstResponder</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">) ?? </span><span style="color: #0000FF">false</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Unable to make self the first responder - reverting to built-in Services submenu.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #001080">allowsContextMenuPlugIns</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Add all your other items here.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> !menu.</span><span style="color: #001080">allowsContextMenuPlugIns</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(NSMenuItem.</span><span style="color: #795E26">separator</span><span style="color: #000000">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> services = </span><span style="color: #795E26">NSMenuItem</span><span style="color: #000000">(</span><span style="color: #795E26">title</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;Services&quot;</span><span style="color: #000000">, </span><span style="color: #795E26">action</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">, </span><span style="color: #795E26">keyEquivalent</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> serviceSubmenu = </span><span style="color: #795E26">NSMenu</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    services.</span><span style="color: #001080">submenu</span><span style="color: #000000"> = serviceSubmenu</span></span>
<span class="line"><span style="color: #000000">    services.</span><span style="color: #001080">representedObject</span><span style="color: #000000"> = textItem.</span><span style="color: #001080">provider</span></span>
<span class="line"><span style="color: #000000">    NSApplication.</span><span style="color: #001080">shared</span><span style="color: #000000">.</span><span style="color: #001080">servicesMenu</span><span style="color: #000000"> = serviceSubmenu</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(services)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Fortunately &#8211; and thanks to the deep elegance of AppKit&#8217;s design &#8211; that&#8217;s <em>it</em>!  It otherwise uses all the same machinery as before (like <code>writeSelection(to:returnType:)</code>).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ Making the view into the first responder is necessary to ensure it&#8217;s the one that gets called [first] about what data is available to the Services etc.  And it&#8217;s also in principle the correct thing to do &#8211; any view that responds to your interactions should generally be [made] first responder as a result.  And it seems to work perfectly in my use-cases.</p>



<p><em>But</em>, be aware that it <em>could</em> cause issues in some applications, if it interacts poorly with whatever other view(s) lose first responder status as a result.  I can only leave that as a warning and potential exercise for each reader, to figure out how to adapt the above to their circumstances as necessary.</p>



<p>(this isn&#8217;t a concern unique to this code, by any means, more the general warning for whenever you manually change the first responder)</p>
</div></div>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ macOS 14 Sonoma has a rendering bug when first opening the Services submenu within a given application session, as shown below.  It&#8217;s not a <em>big</em> deal insofar as most of the items still work, and dismissing the menu and re-opening it fixes the rendering.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="367" height="815" src="https://wadetregaskis.com/wp-content/uploads/2024/03/Broken-Services-submenu-rendering-in-macOS-14-Sonoma.webp" alt="Screenshot of the Services menu in macOS 14 Sonoma showing the rendering glitch bug, whereby the bottom third of the menu is mangled, with many items missing entirely and others vertically truncated or displayed atop each other." class="wp-image-7874" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/Broken-Services-submenu-rendering-in-macOS-14-Sonoma.webp 367w, https://wadetregaskis.com/wp-content/uploads/2024/03/Broken-Services-submenu-rendering-in-macOS-14-Sonoma-115x256.webp 115w, https://wadetregaskis.com/wp-content/uploads/2024/03/Broken-Services-submenu-rendering-in-macOS-14-Sonoma@2x.webp 734w, https://wadetregaskis.com/wp-content/uploads/2024/03/Broken-Services-submenu-rendering-in-macOS-14-Sonoma-115x256@2x.webp 230w" sizes="auto, (max-width: 367px) 100vw, 367px" /></figure>
</div></div></div>



<h2 class="wp-block-heading">Bonuses</h2>



<h3 class="wp-block-heading">Adding a Copy menu item</h3>



<p>Since the contextual menu is empty except for the default Services menu, you&#8217;ll probably want to add in some of the other options that are typically found in the contextual menu &#8211; Copy being perhaps the most prominent and often-used.</p>



<p>Fortunately, it&#8217;s trivial:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">doCopy</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">menuItem</span><span style="color: #000000">: NSMenuItem) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> pb = NSPasteboard.</span><span style="color: #001080">general</span></span>
<span class="line"><span style="color: #000000">    pb.</span><span style="color: #795E26">prepareForNewContents</span><span style="color: #000000">(</span><span style="color: #795E26">with</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    pb.</span><span style="color: #795E26">setString</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">(), </span><span style="color: #795E26">forType</span><span style="color: #000000">: .</span><span style="color: #001080">string</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Then, in your `menu(for:)` method:</span></span>
<span class="line"><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> copyMenuItem = </span><span style="color: #795E26">NSMenuItem</span><span style="color: #000000">(</span><span style="color: #795E26">title</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;Copy&quot;</span><span style="color: #000000">, </span><span style="color: #795E26">action</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #0000FF">Self</span><span style="color: #000000">.</span><span style="color: #795E26">doCopy</span><span style="color: #000000">(_:)), </span><span style="color: #795E26">keyEquivalent</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    copyMenuItem.</span><span style="color: #001080">target</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span></span>
<span class="line"><span style="color: #000000">    copyMenuItem.</span><span style="color: #001080">isEnabled</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(copyMenuItem)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>One obvious missing piece is localisation of the &#8220;Copy&#8221; label; left as an exercise for the reader.</p>



<h3 class="wp-block-heading">Adding a Share menu item</h3>



<p>It&#8217;s similarly simple to add a standard Share menu item:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">showSharePopup</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">menuItem</span><span style="color: #000000">: NSMenuItem) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> picker = </span><span style="color: #795E26">NSSharingServicePicker</span><span style="color: #000000">(</span><span style="color: #795E26">items</span><span style="color: #000000">: [</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">()])</span></span>
<span class="line"><span style="color: #000000">    picker.</span><span style="color: #795E26">show</span><span style="color: #000000">(</span><span style="color: #795E26">relativeTo</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">bounds</span><span style="color: #000000">, </span><span style="color: #795E26">of</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">, </span><span style="color: #795E26">preferredEdge</span><span style="color: #000000">: .</span><span style="color: #001080">maxY</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Then, in your `menu(for:)` method:</span></span>
<span class="line"><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> picker = </span><span style="color: #795E26">NSSharingServicePicker</span><span style="color: #000000">(</span><span style="color: #795E26">items</span><span style="color: #000000">: [</span><span style="color: #A31515">&quot;🐞&quot;</span><span style="color: #000000">])</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> pickerMenuItem = picker.</span><span style="color: #001080">standardShareMenuItem</span></span>
<span class="line"><span style="color: #000000">    pickerMenuItem.</span><span style="color: #001080">target</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span></span>
<span class="line"><span style="color: #000000">    pickerMenuItem.</span><span style="color: #001080">action</span><span style="color: #000000"> = </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #0000FF">Self</span><span style="color: #000000">.</span><span style="color: #795E26">showSharePopup</span><span style="color: #000000">(_:))</span></span>
<span class="line"><span style="color: #000000">    pickerMenuItem.</span><span style="color: #001080">isEnabled</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(pickerMenuItem)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This one benefits from using the standard, AppKit-provided menu item, so you don&#8217;t need to handle localising its label.</p>



<p>The 🐞 is there for two reasons:</p>



<ol class="wp-block-list">
<li>You have to provide <code><a href="https://developer.apple.com/documentation/appkit/nssharingservicepicker" data-wpel-link="external" target="_blank" rel="external noopener">NSSharingServicePicker</a></code> the nominal item(s) to share up front, in order to initialise it (and so it can tailor its display to the content).  <code><a href="https://developer.apple.com/documentation/appkit/nssharingservicepicker/4031316-standardsharemenuitem" data-wpel-link="external" target="_blank" rel="external noopener">standardShareMenuItem</a></code> should actually be a class member variable, not an instance member variable &#8211; a design flaw in AppKit.<br><br>You don&#8217;t want to invoke the <code>textProvider</code> closure unless you really need to (it could be time-consuming to run, so you don&#8217;t want to run it unnecessarily nor while the user is trying to navigate the contextual menu lest it cause GUI hiccups).  So an equivalent placeholder value &#8211; any other string, in this case &#8211; is better, and suffices.</li>



<li>I use the ladybug emoji so that it stands out if the value ever somehow gets shown to the user (it&#8217;s a bug, get it? 😄).</li>
</ol>



<h3 class="wp-block-heading">Adding a Look Up menu item</h3>



<p>This one&#8217;s a little bit more iffy.  If you think you genuinely have need of a Look Up item, consider whether you should be instead making the text selectable and simply utilising the built-in contextual menu support that selectable text views have in SwiftUI.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">lookUp</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">menuItem</span><span style="color: #000000">: NSMenuItem) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">showDefinition</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: </span><span style="color: #795E26">NSAttributedString</span><span style="color: #000000">(</span><span style="color: #795E26">string</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">()),</span></span>
<span class="line"><span style="color: #000000">                        </span><span style="color: #795E26">at</span><span style="color: #000000">: </span><span style="color: #795E26">NSPoint</span><span style="color: #000000">(</span><span style="color: #795E26">x</span><span style="color: #000000">: CGFloat.</span><span style="color: #001080">infinity</span><span style="color: #000000">, </span><span style="color: #795E26">y</span><span style="color: #000000">: CGFloat.</span><span style="color: #001080">infinity</span><span style="color: #000000">))</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Then, in your `menu(for:)` method:</span></span>
<span class="line"><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> lookupItem = </span><span style="color: #795E26">NSMenuItem</span><span style="color: #000000">(</span><span style="color: #795E26">title</span><span style="color: #000000">: submenu == menu ? </span><span style="color: #A31515">&quot;Look Up “</span><span style="color: #0000FF">\(self</span><span style="color: #000000FF">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000FF">()</span><span style="color: #0000FF">)</span><span style="color: #A31515">”&quot;</span><span style="color: #000000"> : </span><span style="color: #A31515">&quot;Look Up&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                </span><span style="color: #795E26">action</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #0000FF">Self</span><span style="color: #000000">.</span><span style="color: #795E26">lookUp</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                </span><span style="color: #795E26">keyEquivalent</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    lookupItem.</span><span style="color: #001080">target</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span></span>
<span class="line"><span style="color: #000000">    lookupItem.</span><span style="color: #001080">isEnabled</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(lookupItem)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This doesn&#8217;t display perfectly.  The use of infinities for the <code>at</code> argument causes it to draw a small yellow box at the top left of the view, layered underneath the contextual menu but usually still visible.  That&#8217;s a hack for cases where you can&#8217;t determine where the text is, or the text being looked up doesn&#8217;t exactly match what&#8217;s shown in the view.</p>



<p>If you know the <em>actual</em> location of the selected text, you can specify that instead to get the yellow text drawn in that location.  But beware: the text from <code>textProvider</code> must <em>exactly</em> match what&#8217;s rendered in the view, otherwise the yellow overlaid box &#8211; intended to look like a highlight effect &#8211; will look weird, because it [re]renders the text based on what <code>textProvider</code> returned.  It also might not correctly match the font, in any case.</p>



<p>Thus why I caution against using the reinvention of this particular wheel.</p>


<ol class="wp-block-footnotes"><li id="0c0ed089-5fc2-434e-8cfb-699487865d79">Possibly borrowed from earlier computers that had multi-mouse-button support, such as some Unixes, but I suspect just coincidence.  I vaguely recall that they were the philosophical antithesis of Apple w.r.t. mouse buttons, with some *nix GUIs <em>requiring</em> at least three mouse buttons for their basic function.  I seem to recall some of them labelling the buttons primary, secondary, and tertiary. <a href="#0c0ed089-5fc2-434e-8cfb-699487865d79-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="2d6f612a-7fb7-498e-bdd6-68f54de88f78">I doubt I came up with this metaphor &#8211; although it&#8217;s pretty obvious in any case &#8211; but it&#8217;s worth considering if it&#8217;s more than just a cute superficial analogy.  AppKit forms the meaningful contents of the sandwich, providing its flavour, substance, and value, while SwiftUI serves only as the bread, there mainly just to convey the contents. 🤔 <a href="#2d6f612a-7fb7-498e-bdd6-68f54de88f78-link" aria-label="Jump to footnote reference 2">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/including-services-in-contextual-menus-in-swiftui/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7861</post-id>	</item>
		<item>
		<title>Hiding SwiftUI views</title>
		<link>https://wadetregaskis.com/hiding-swiftui-views/</link>
					<comments>https://wadetregaskis.com/hiding-swiftui-views/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 15 Feb 2024 01:42:38 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[AnyView]]></category>
		<category><![CDATA[EmptyView]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7711</guid>

					<description><![CDATA[There are several ways to hide a SwiftUI view, although they don&#8217;t all agree on what it means to hide the view. Do you want it to be invisible, or actually not there? To make it invisible you need only set its opacity to zero or use the hidden modifier. But the view will still&#8230; <a class="read-more-link" href="https://wadetregaskis.com/hiding-swiftui-views/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>There are several ways to hide a SwiftUI view, although they don&#8217;t all agree on what it <em>means</em> to hide the view.  Do you want it to be invisible, or actually not there?</p>



<p>To make it invisible you need only set its <a href="https://developer.apple.com/documentation/swiftui/view/opacity(_:)" data-wpel-link="external" target="_blank" rel="external noopener">opacity</a> to zero or use the <code><a href="https://developer.apple.com/documentation/swiftui/view/hidden()" data-wpel-link="external" target="_blank" rel="external noopener">hidden</a></code> modifier.  But the view will still be laid out just the same, and take up space in the GUI.</p>



<p>If you want to <em>actually</em> hide the view, so that it disappears completely, you can either not emit the view at all (e.g. conditionalise its existence with <code>if</code> or <code>switch</code>) or you can replace it with <code><a href="https://developer.apple.com/documentation/swiftui/emptyview/" data-wpel-link="external" target="_blank" rel="external noopener">EmptyView</a></code>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="654" height="545" src="https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep.avif" alt="" class="wp-image-7715" srcset="https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep.avif 654w, https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep-256x213.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep@2x.avif 1308w, https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep-256x213@2x.avif 512w" sizes="auto, (max-width: 654px) 100vw, 654px" /></figure>
</div>


<p><code>EmptyView</code> is pretty marvellous in this respect.  Notice how even though it&#8217;s a real view &#8211; an actual value that you emit from a view builder &#8211; layout collections (e.g. <code>HStack</code>) don&#8217;t &#8220;see&#8221; it &#8211; there&#8217;s no visible gap, from doubling up on the cell padding, like there would be if they simply treated <code>EmptyView</code>s like 0 wide ⨉ 0 high views.</p>



<p>You might ask, when would you actually <em>need</em> EmptyView?  Wouldn&#8217;t you&#8217;d just use conditional code to hide a view, e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">MyView</span><span style="color: #000000">: View {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> model: Model?</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: some View {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> model {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #795E26">Text</span><span style="color: #000000">(model.</span><span style="color: #001080">text</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Now you can just use MyView(model: someOptional),</span></span>
<span class="line"><span style="color: #008000">// without having to unwrap it before every use.</span></span></code></pre></div>



<p>The above is making use of the &#8220;ViewBuilder mode&#8221;, to implicitly yield zero or more views which are automagically aggregated into a <code><a href="https://developer.apple.com/documentation/swiftui/tupleview" data-wpel-link="external" target="_blank" rel="external noopener">TupleView</a></code> (if you emit more than one), an optional view (if you dynamically emit zero or one), etc.  That&#8217;s very convenient, in simple cases like that.</p>



<p>But, &#8220;ViewBuilder mode&#8221; comes with a <em>lot</em> of limitations, including:</p>



<ul class="wp-block-list">
<li>Compiler diagnostics are far inferior to regular Swift code.</li>



<li>You cannot return early (i.e. use <code>return</code> statements) except by throwing an exception, which is not always semantically appropriate.</li>



<li>You cannot have nested functions.</li>



<li>You sometimes cannot use full Swift control flow syntax (e.g. switch statements).</li>
</ul>



<p>Fortunately, you can opt out of &#8220;ViewBuilder mode&#8221; by simply using an explicit <code>return</code> statement, but then you have to follow the usual rules for opaque return types, i.e. that the value be of the same type for every <code>return</code> statement.  You can use <code>AnyView</code> in concert with <code>EmptyView</code> to straighten the compiler&#8217;s knickers regarding the return type<sup data-fn="d83a6861-4b9d-4634-a63e-e5247eff8def" class="fn"><a href="#d83a6861-4b9d-4634-a63e-e5247eff8def" id="d83a6861-4b9d-4634-a63e-e5247eff8def-link">1</a></sup>, e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">MyView</span><span style="color: #000000">: View {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> model: Model?</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: some View {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">guard</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> model </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #795E26">AnyView</span><span style="color: #000000">(</span><span style="color: #795E26">EmptyView</span><span style="color: #000000">())</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">        </span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #795E26">AnyView</span><span style="color: #000000">(&lt;actual view contents&gt;)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>…or &#8211; if you don&#8217;t mind the return type being <code>Optional</code>, which SwiftUI itself doesn&#8217;t &#8211; you can use a partially opaque return type:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">MyView</span><span style="color: #000000">: View {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> model: Model?</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: </span><span style="color: #267F99">Optional</span><span style="color: #000000">&lt;some View&gt; {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">guard</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> model </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> Body.</span><span style="color: #001080">none</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">        </span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> &lt;actual view contents&gt;</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Thanks to <a href="https://forums.swift.org/u/dmt/summary" data-wpel-link="external" target="_blank" rel="external noopener">Dima Galimzianov</a> for <a href="https://forums.swift.org/t/cannot-use-nil-or-none-with-optional-some-t/70098/2" data-wpel-link="external" target="_blank" rel="external noopener">helping me figure out how to do that</a>.</p>



<p>Note that in the trivial cases shown above there&#8217;s no <em>particularly</em> compelling reason to use these forms instead of the &#8220;ViewBuilder mode&#8221;, but you could hopefully imagine how, as the conditional logic gets more complicated, it becomes increasingly impractical to avoid using guard, or early returns otherwise.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Note that while <code>AnyView</code> has a lot of FUD<sup data-fn="41306d14-0450-4ed5-a469-b2a06045dc54" class="fn"><a href="#41306d14-0450-4ed5-a469-b2a06045dc54" id="41306d14-0450-4ed5-a469-b2a06045dc54-link">2</a></sup> associated with it, as far as I can tell there&#8217;s really nothing wrong with it.  Most often it&#8217;s claimed to cause performance problems, but I&#8217;ve never detected that in my use, nor seen anyone provide a real example case of such.  And multiple people have gone looking for performance problems with <code>AnyView</code> and found nothing.  e.g. <a href="https://www.linkedin.com/in/nalexn/" data-wpel-link="external" target="_blank" rel="external noopener">Alexey Naumov</a>&#8216;s <a href="https://nalexn.github.io/anyview-vs-group/" data-wpel-link="external" target="_blank" rel="external noopener">Performance Battle: AnyView vs Group</a>.</p>
</div></div>



<p>In case you&#8217;re wondering, there&#8217;s no difference in show/hide behaviour either &#8211; it just works!</p>



<figure class="wp-block-video aligncenter fix-wolf-video-size"><video height="1086" style="aspect-ratio: 1304 / 1086;" width="1304" autoplay loop preload="auto" src="https://wadetregaskis.com/wp-content/uploads/2024/02/The-Big-Wolfie-Reveal.mp4" playsinline></video></figure>



<p>It&#8217;s possible that there&#8217;s some situations in which using <code>EmptyView</code> might cause animation issues, by confusing SwiftUI as to what the relationship is between view hierarchies over time, but I haven&#8217;t found that to be the case in practice.  And if it ever does crop up, you can always just explicitly <a href="https://developer.apple.com/documentation/swiftui/view/id(_:)" data-wpel-link="external" target="_blank" rel="external noopener">id</a> your views.</p>



<p><code>EmptyView</code> is one of those little sleeper features that seems irrelevant until you stumble upon a need for it, and then it&#8217;s <em>really</em> nice to have.</p>


<ol class="wp-block-footnotes"><li id="d83a6861-4b9d-4634-a63e-e5247eff8def">There&#8217;s technically another option: use a concrete return type instead.  But, that&#8217;s usually impractical &#8211; SwiftUI uses opaque return types <em>a lot</em>, such as for virtually all view modifiers, which force you to use opaque return types in turn.  And even when <em>that</em> isn&#8217;t an issue, good luck deducing the correct fully-qualified type name even for something as simple as a <code>LabeledContent</code>. <a href="#d83a6861-4b9d-4634-a63e-e5247eff8def-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="41306d14-0450-4ed5-a469-b2a06045dc54">Literally, &#8220;Fear, Uncertainty, and Doubt&#8221;.  Used here in that face-value sense, not to suggest any malice on anyone&#8217;s part. <a href="#41306d14-0450-4ed5-a469-b2a06045dc54-link" aria-label="Jump to footnote reference 2">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/hiding-swiftui-views/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		<enclosure url="https://wadetregaskis.com/wp-content/uploads/2024/02/The-Big-Wolfie-Reveal.mp4" length="186396" type="video/mp4" />

			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7711</post-id>	</item>
		<item>
		<title>SwiftUI drag &#038; drop does not support file promises</title>
		<link>https://wadetregaskis.com/swiftui-drag-drop-does-not-support-file-promises/</link>
					<comments>https://wadetregaskis.com/swiftui-drag-drop-does-not-support-file-promises/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 04 Feb 2024 19:04:31 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Drag & drop]]></category>
		<category><![CDATA[NSFilePromiseProvider]]></category>
		<category><![CDATA[NSItemProvider]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Transferable]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7646</guid>

					<description><![CDATA[SwiftUI doesn’t offer anything equivalent to NSFilePromiseProvider, i.e. to write data to the drop destination. You have to ditch SwiftUI and use AppKit&#8217;s drag &#38; drop APIs instead. FB13583826. Is that it? I know that&#8217;s not a very helpful in some sense, but I wasted days trying to figure out how to implement this very&#8230; <a class="read-more-link" href="https://wadetregaskis.com/swiftui-drag-drop-does-not-support-file-promises/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>SwiftUI doesn’t offer anything equivalent to <code><a href="https://developer.apple.com/documentation/appkit/nsfilepromiseprovider" data-wpel-link="external" target="_blank" rel="external noopener">NSFilePromiseProvider</a></code>, i.e. to write data to the drop destination.  You have to ditch SwiftUI and use AppKit&#8217;s drag &amp; drop APIs instead.</p>



<p>FB13583826.</p>



<h2 class="wp-block-heading">Is that it?</h2>



<p>I know that&#8217;s not a very helpful in some sense, but I wasted <em>days</em> trying to figure out how to implement this very basic drag and drop functionality, in SwiftUI.  From what I <a href="https://stackoverflow.com/questions/76327255/swiftui-receiving-nsfilepromisereceiver-via-nsitemprovider" data-wpel-link="external" target="_blank" rel="external noopener">found</a> <a href="https://stackoverflow.com/questions/69774792/use-nsitemprovider-in-combination-with-nsfilepromiseprovider" data-wpel-link="external" target="_blank" rel="external noopener">online</a>, I&#8217;m not the only person who&#8217;s struggled with this.  So hopefully this post saves others from long and frustrating searches.</p>



<p>A big part of the difficulty in answering this simple question &#8211; does SwiftUI support this or not &#8211; is that a lot of documentation (first &amp; third party) around SwiftUI&#8217;s APIs uses the word &#8220;promise&#8221; but not in the same way.  They merely mean that the pasteboard data is populated on first access, not that it actually follows the file promise protocol.</p>



<h2 class="wp-block-heading">Best alternative (other than ditching SwiftUI)</h2>



<p>The closest you can get is to write data into some arbitrary location that you have to choose blindly (not having any idea where the actual destination is), and then rely on the receiving app to move or copy the file from there.  It doesn&#8217;t matter, in this regard, whether you use <a href="https://developer.apple.com/documentation/swiftui/drag-and-drop#moving-transferable-items" data-wpel-link="external" target="_blank" rel="external noopener">the <code>Transferable</code>-based APIs</a> or <a href="https://developer.apple.com/documentation/swiftui/drag-and-drop#moving-items-using-item-providers" data-wpel-link="external" target="_blank" rel="external noopener">the <code>NSItemProvider</code>-based APIs</a>.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">view.</span><span style="color: #001080">onDrag</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> provider = </span><span style="color: #795E26">NSItemProvider</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    provider.</span><span style="color: #795E26">registerDataRepresentation</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: UTType.</span><span style="color: #001080">fileURL</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> tmpDir = </span><span style="color: #AF00DB">try</span><span style="color: #000000"> FileManager.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #001080">url</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: .</span><span style="color: #001080">itemReplacementDirectory</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                     </span><span style="color: #795E26">in</span><span style="color: #000000">: .</span><span style="color: #001080">userDomainMask</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                     </span><span style="color: #795E26">appropriateFor</span><span style="color: #000000">: URL.</span><span style="color: #001080">temporaryDirectory</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                     </span><span style="color: #795E26">create</span><span style="color: #000000">: </span><span style="color: #0000FF">true</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #008000">// You&#39;ll need to provide the `suggestedFileName` based on the particulars of your use-case.</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> file = tmpDir.</span><span style="color: #795E26">appending</span><span style="color: #000000">(</span><span style="color: #795E26">component</span><span style="color: #000000">: suggestedFileName)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">try</span><span style="color: #000000"> bytes.</span><span style="color: #795E26">write</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000">: file, </span><span style="color: #795E26">options</span><span style="color: #000000">: .</span><span style="color: #001080">withoutOverwriting</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">$0</span><span style="color: #000000">(file.</span><span style="color: #001080">dataRepresentation</span><span style="color: #000000">, </span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        } </span><span style="color: #AF00DB">catch</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">$0</span><span style="color: #000000">(</span><span style="color: #0000FF">nil</span><span style="color: #000000">, error)</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">nil</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> provider</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This makes it impossible to avoid unnecessary file copies, and requires you to keep the file around permanently, since you have no idea when the receiver is done with it.</p>



<p>e.g. if the Finder is the drop destination, you have no idea what volume the promised file was dropped on, so you don’t know which volume to create it on.  If you guess wrong, the Finder is forced to copy the file.  Not only does this confuse the user (by showing a copy badge on the mouse pointer) but it wastes time (in performing the copy) and the original file is left in place, requiring manual clean-up (which is impossible to do correctly because you have no idea when the receiving application is done with the temporary file &#8211; and the receiver might hold a reference to it permanently).</p>



<h2 class="wp-block-heading">Understanding the SwiftUI drag &amp; drop APIs</h2>



<p>Tangentially, I found most documentation on SwiftUI&#8217;s drag &amp; drop APIs &#8211; <em>especially</em> Apple&#8217;s &#8211; to be very poorly written, which is particularly frustrating in this case because the APIs are not intuitive in the slightest.</p>



<p>And most code / usage examples focus exclusively on trivial, disinteresting cases (e.g. dragging images around within a single window).</p>



<p>Only after I&#8217;d spend days reverse-engineering the APIs to figure out how they <em>actually</em> work and how they&#8217;re supposed to be used, did I stumble upon <a href="https://www.humancode.us/about.html" data-wpel-link="external" target="_blank" rel="external noopener">Dave Rahardja</a>&#8216;s <a href="https://www.humancode.us/2023/07/08/all-about-nsitemprovider.html" data-wpel-link="external" target="_blank" rel="external noopener">All about Item Providers</a>.  It&#8217;s <em>by far</em> the best documentation I&#8217;ve found on drag &amp; drop in SwiftUI.  Note though that Dave also uses the word &#8220;promise&#8221; a lot even though he&#8217;s never actually talking about actual file promises.</p>



<p>Also, I found that the newer and ostensibly better <code><a href="https://developer.apple.com/documentation/coretransferable/transferable" data-wpel-link="external" target="_blank" rel="external noopener">Transferable</a></code>-based API was harder to understand and harder to use than the <code><a href="https://developer.apple.com/documentation/foundation/nsitemprovider/" data-wpel-link="external" target="_blank" rel="external noopener">NSItemProvider</a></code>-based APIs.  I recommend going straight to, and sticking with, the latter.  Your code will be simpler and more reliable.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/swiftui-drag-drop-does-not-support-file-promises/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7646</post-id>	</item>
		<item>
		<title>NSImage is dangerous</title>
		<link>https://wadetregaskis.com/nsimage-is-dangerous/</link>
					<comments>https://wadetregaskis.com/nsimage-is-dangerous/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 23 Jan 2024 21:53:57 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[AppKit]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[NSBitmapImageRep]]></category>
		<category><![CDATA[NSImage]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[unsafe]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7501</guid>

					<description><![CDATA[NSImage is formally documented as largely not thread-safe: The following classes and functions are generally not thread-safe. In most cases, you can use these classes from any thread as long as you use them from only one thread at a time. Check the class documentation for additional details. Apple&#8217;s Threading Programming Guide &#62; Appendix A:&#8230; <a class="read-more-link" href="https://wadetregaskis.com/nsimage-is-dangerous/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p><code><a href="https://developer.apple.com/documentation/appkit/nsimage" data-wpel-link="external" target="_blank" rel="external noopener">NSImage</a></code> is <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB" data-wpel-link="external" target="_blank" rel="external noopener">formally documented</a> as largely <em>not</em> thread-safe:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The following classes and functions are generally not thread-safe. In most cases, you can use these classes from any thread as long as you use them from only one thread at a time. Check the class documentation for additional details.</p>
<cite>Apple&#8217;s <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i-CH1-SW1" data-wpel-link="external" target="_blank" rel="external noopener">Threading Programming Guide</a> &gt; <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1" data-wpel-link="external" target="_blank" rel="external noopener">Appendix A: Thread Safety Summary</a>, subsection <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB" data-wpel-link="external" target="_blank" rel="external noopener">Application Kit Framework Thread Safety</a></cite></blockquote>



<p>What &#8220;in most cases&#8221; means is left to the reader&#8217;s imagination.  Apple adds a little addendum for <code>NSImage</code> specifically:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>One thread can create an <code><a href="https://developer.apple.com/documentation/appkit/nsimage" data-wpel-link="external" target="_blank" rel="external noopener">NSImage</a></code> object, draw to the image buffer, and pass it off to the main thread for drawing. The underlying image cache is shared among all threads. For more information about images and how caching works, see <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40003290" data-wpel-link="external" target="_blank" rel="external noopener">Cocoa Drawing Guide</a>. </p>
<cite><a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-126728" data-wpel-link="external" target="_blank" rel="external noopener">NSImage Restrictions</a></cite></blockquote>



<p>For a start, it&#8217;s talking only about <em>creating an NSImage from scratch</em>, not loading it from serialised form (e.g. a file, a pasteboard, etc).  It doesn&#8217;t even deign to mention those other, much more common cases.</p>



<p>And even for that one mentioned use case, what does it mean, exactly?  What &#8220;image cache&#8221; is it referring to?</p>



<p>I don&#8217;t have authoritative answers.  The documentation is so infuriatingly vague that Apple could do basically anything to the implementation, between macOS updates, and claim to have broken no promises.</p>



<p>What I do have is some empirical data and the results of some reverse engineering (shout out to <a href="https://www.hopperapp.com" data-wpel-link="external" target="_blank" rel="external noopener">Hopper</a>), from macOS 14.2 Sonoma.</p>



<h2 class="wp-block-heading">NSImage 101</h2>



<p><code>NSImage</code>s can represent a wide range of imagery.  Most uses of them are probably for bitmap data (i.e. what you find in common image formats like <a href="https://en.wikipedia.org/wiki/WebP" data-wpel-link="external" target="_blank" rel="external noopener">WebP</a> &amp; <a href="https://en.wikipedia.org/wiki/AVIF" data-wpel-link="external" target="_blank" rel="external noopener">AVIF</a>), but <code>NSImage</code> also supports &#8216;raw&#8217; images (e.g. <a href="https://www.nikonusa.com/learn-and-explore/c/products-and-innovation/nikon-electronic-format-nef" data-wpel-link="external" target="_blank" rel="external noopener">Nikon NEF</a>) as well as vector data (e.g. <a href="https://en.wikipedia.org/wiki/SVG" data-wpel-link="external" target="_blank" rel="external noopener">SVG</a> &amp; <a href="https://en.wikipedia.org/wiki/PDF" data-wpel-link="external" target="_blank" rel="external noopener">PDF</a>).  It also has a plug-in mechanism of sorts, so the supported image formats can be extended dynamically at runtime.</p>



<p>You can fetch the full list of supported types from <a href="https://developer.apple.com/documentation/appkit/nsimage/1519988-imagetypes" data-wpel-link="external" target="_blank" rel="external noopener">NSImage.imageTypes</a> &#8211; although it doesn&#8217;t distinguish between those it can read vs those it can write.  Fortunately, <code>sips --formats</code> in Terminal gives you the same list with additional metadata.  On my machine that list happens to be:</p>



<pre class="wp-block-preformatted">Supported Formats:
-------------------------------------------
com.adobe.pdf                pdf   Writable
com.adobe.photoshop-image    psd   Writable
com.adobe.raw-image          dng   
com.apple.atx                --    Writable
com.apple.icns               icns  Writable
com.apple.pict               pict  
com.canon.cr2-raw-image      cr2   
com.canon.cr3-raw-image      cr3   
com.canon.crw-raw-image      crw   
com.canon.tif-raw-image      tif   
com.compuserve.gif           gif   Writable
com.dxo.raw-image            dxo   
com.epson.raw-image          erf   
com.fuji.raw-image           raf   
com.hasselblad.3fr-raw-image 3fr   
com.hasselblad.fff-raw-image fff   
com.ilm.openexr-image        exr   Writable
com.kodak.raw-image          dcr   
com.konicaminolta.raw-image  mrw   
com.leafamerica.raw-image    mos   
com.leica.raw-image          raw   
com.leica.rwl-raw-image      rwl   
com.microsoft.bmp            bmp   Writable
com.microsoft.cur            --    
com.microsoft.dds            dds   Writable
com.microsoft.ico            ico   Writable
com.nikon.nrw-raw-image      nrw   
com.nikon.raw-image          nef   
com.olympus.or-raw-image     orf   
com.olympus.raw-image        orf   
com.olympus.sr-raw-image     orf   
com.panasonic.raw-image      raw   
com.panasonic.rw2-raw-image  rw2   
com.pentax.raw-image         pef   
com.phaseone.raw-image       iiq   
com.samsung.raw-image        srw   
com.sgi.sgi-image            sgi   
com.sony.arw-raw-image       arw   
com.sony.raw-image           srf   
com.sony.sr2-raw-image       sr2   
com.truevision.tga-image     tga   Writable
org.khronos.astc             astc  Writable
org.khronos.ktx              ktx   Writable
org.khronos.ktx2             --    Writable
org.webmproject.webp         webp  
public.avci                  avci  
public.avif                  avif  
public.avis                  --    
public.heic                  heic  Writable
public.heics                 heics Writable
public.heif                  heif  
public.jpeg                  jpeg  Writable
public.jpeg-2000             jp2   Writable
public.jpeg-xl               jxl   
public.mpo-image             mpo   
public.pbm                   pbm   Writable
public.png                   png   Writable
public.pvr                   pvr   Writable
public.radiance              pic   
public.svg-image             svg   
public.tiff                  tiff  Writable</pre>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Sidenote:  notice how it supports modern formats like WebP, AVIF, and JPEG-XL, but <em>only</em> for reading, not writing. 😕</p>



<p>It used to be that <code>NSImage</code> was pretty much a one-stop-shop for typical app image I/O needs, but Apple have for some reason crippled it over the years.  I feel like this is part of a larger trend of Apple providing increasingly less functionality, more complexity, and less coherence in their frameworks.</p>
</div></div>



<p>An <code>NSImage</code> can have multiple &#8216;<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Images/Images.html#//apple_ref/doc/uid/TP40003290-CH208-SW9" data-wpel-link="external" target="_blank" rel="external noopener">representations</a>&#8216;.  These are essentially just different versions of the image.  One representation might be the original vector form (e.g. an <code><a href="https://developer.apple.com/documentation/appkit/nspdfimagerep" data-wpel-link="external" target="_blank" rel="external noopener">NSPDFImageRep</a></code>).  Another might be a rasterisation of that at a certain resolution.  Yet another might be a rasterisation at a different resolution.</p>



<p>It gets more complicated, however, because some representations are merely proxies for <em>other</em> frameworks&#8217; representations, like <a href="https://developer.apple.com/documentation/coregraphics/cgimage/" data-wpel-link="external" target="_blank" rel="external noopener"><code>CGImage</code></a>, <a href="https://developer.apple.com/documentation/coreimage/ciimage/" data-wpel-link="external" target="_blank" rel="external noopener"><code>CIImage</code></a> or somesuch.</p>



<p>Nonetheless, for the simple case of a static bitmap image read from a file, generally <code>NSImage</code> produces just one representation, which is the full bitmap (as an <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep" data-wpel-link="external" target="_blank" rel="external noopener">NSBitmapImageRep</a></code>).  That&#8217;s the only case I&#8217;m going to discuss here, for simplicity&#8217;s sake (though likely the lessons apply to the other cases too).</p>



<h2 class="wp-block-heading">Accessing / using an NSImage</h2>



<p>Generally to use an <code>NSImage</code> you need a bitmap representation.  e.g. to actually draw it to the screen.</p>



<p>Unless you manually create an <code>NSImage</code> from an in-memory bitmap, the bitmap representation is not loaded initially, but rather only when first needed.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ You can usually obtain the <code>NSBitmapImageRep</code> (via e.g. <code><a href="https://developer.apple.com/documentation/appkit/nsimage/1519961-bestrepresentation" data-wpel-link="external" target="_blank" rel="external noopener">bestRepresentation(for: .infinity, context: nil, hints: nil)</a></code>) even before it&#8217;s actually been loaded &#8211; initially it&#8217;s largely just a shell, that knows its metadata (e.g. dimensions and colour space) but not yet its actual imagery.</p>
</div></div>



<p>Ultimately the load is triggered when something asks for the raw bytes of the bitmap (either you, directly in your code, or indirectly when you e.g. ask AppKit to draw the image).</p>



<p>Fetching the raw bytes of a bitmap from <code>NSBitmapImageRep</code> is not a trivial exercise.  I&#8217;m going to gloss over the complexities (like planar data formats) and just talk about the common case of single-plane (&#8220;interleaved channels&#8221;) bitmaps.</p>



<p>For those, you can access the raw bytes via the <a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep/1395421-bitmapdata" data-wpel-link="external" target="_blank" rel="external noopener"><code>bitmapData</code></a> property.</p>



<p>Nominally, <code>bitmapData</code> just returns a <code>uint8_t*</code>.</p>



<p>In fact, <code>bitmapData</code> calls a <em>lot</em> of internal methods in a complicated fashion, with many possible code paths.  What exactly it does depends on the underlying source of data, but in a nutshell it checks if the desired data has already been created / loaded (it&#8217;s cached in an instance variable) and if not it loads it, e.g. by calling <code><a href="https://developer.apple.com/documentation/imageio/1465011-cgimagesourcecreateimageatindex" data-wpel-link="external" target="_blank" rel="external noopener">CGImageSourceCreateImageAtIndex</a></code>.</p>



<p>That&#8217;s what makes <code>NSImage</code> dangerous to use from multiple threads simultaneously.</p>



<figure class="wp-block-pullquote"><blockquote><p>There is no mutual exclusion protecting any of these code paths.</p></blockquote></figure>



<p>Reading the <em>cached</em> bitmap data is essentially read-only (just some retain/autorelease traffic) so it&#8217;s safe to call from multiple threads concurrently (as long as something ensures the <code>NSBitmapImageRep</code> is kept alive the whole time <em>and not mutated</em>)</p>



<p>But loading or modifying it is not.  As such, if you call <code>bitmapData</code> from multiple threads concurrently, and you don&#8217;t know for sure that it&#8217;s already fully loaded, you get a <a href="https://www.avanderlee.com/swift/race-condition-vs-data-race/" data-wpel-link="external" target="_blank" rel="external noopener">data race</a> (also known as a &#8220;WTF does my app crash randomly?!&#8221; condition).</p>



<p>The consequences of that race vary.  Maybe you &#8220;win&#8221; the race &#8211; one thread happens to run virtually to completion of <code>bitmapData</code> first, storing the fresh backing data into the caching instance member, and then all the other threads run and just return that same value &#8211; the ideal situation as everything works as intended.</p>



<p>Maybe you &#8220;lose&#8221; the race: every concurrent thread checks simultaneously and sees there&#8217;s no cached value, so they all &#8211; in parallel and redundant to each other &#8211; load the bitmap data and store it into the cache.  They each return the one they created, even though ultimately only one thread wins &#8211; the <em>last</em> one to write into the cache &#8211; and the duplicate bitmap data that all the earlier threads created is deallocated.  Even though pointers to them have been returned to you.  And you might be in the middle of using them.  Causing you to crash with a memory protection fault (or worse, read from some random other memory allocation that happened to be placed at the same address afterwards, reading essentially garbage).</p>



<p>Hypocritically, this is because Apple don&#8217;t follow <a href="https://developer.apple.com/documentation/swift/calling-functions-with-pointer-parameters#Pass-a-Constant-Pointer-as-a-Parameter" data-wpel-link="external" target="_blank" rel="external noopener">their own rules on escaping pointers</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The pointer you pass to the function is only guaranteed to be valid for the duration of the function call. Do not persist the pointer and access it after the function has returned.</p>
<cite>Apple&#8217;s <a href="https://developer.apple.com/documentation/swift/swift-standard-library" data-wpel-link="external" target="_blank" rel="external noopener">Swift Standard Library</a> &gt; <a href="https://developer.apple.com/documentation/swift/manual-memory-management" data-wpel-link="external" target="_blank" rel="external noopener">Manual Memory Management</a> &gt; <a href="https://developer.apple.com/documentation/swift/calling-functions-with-pointer-parameters" data-wpel-link="external" target="_blank" rel="external noopener">Calling Functions With Pointer Parameters</a>, subsection <a href="https://developer.apple.com/documentation/swift/calling-functions-with-pointer-parameters#Pass-a-Constant-Pointer-as-a-Parameter" data-wpel-link="external" target="_blank" rel="external noopener">Pass a Constant Pointer as a Parameter</a>.</cite></blockquote>



<p>The source code for the relevant <code>NSBitmapImageRep</code> methods is essentially:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:4;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">- (</span><span style="color: #0000FF">uint8_t</span><span style="color: #000000">*)bitmapData {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">uint8_t</span><span style="color: #000000"> **result = </span><span style="color: #0000FF">nil</span><span style="color: #000000">;</span></span>
<span class="line"><span style="color: #000000">    [</span><span style="color: #0000FF">self</span><span style="color: #000000"> </span><span style="color: #795E26">getBitmapDataPlanes:</span><span style="color: #000000">&amp;result];</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> *result;</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">- (</span><span style="color: #0000FF">void</span><span style="color: #000000">)getBitmapDataPlanes:(</span><span style="color: #0000FF">uint8_t</span><span style="color: #000000">***)output {</span></span>
<span class="line"><span style="color: #000000">    [</span><span style="color: #0000FF">self</span><span style="color: #000000"> </span><span style="color: #795E26">_performBlockUsingBackingMutableData:</span><span style="color: #000000">^</span><span style="color: #795E26">void</span><span style="color: #000000"> (</span><span style="color: #0000FF">uint8_t</span><span style="color: #000000">* </span><span style="color: #001080">dataPlanes</span><span style="color: #000000">[</span><span style="color: #098658">5</span><span style="color: #000000">]) {</span></span>
<span class="line"><span style="color: #000000">        *output = dataPlanes;</span></span>
<span class="line"><span style="color: #000000">    }];</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="has-text-align-center has-x-large-font-size">🤦‍♂️</p>



<figure class="wp-block-pullquote"><blockquote><p>The <code>bitmapData</code> property and <code>getBitmapDataPlanes</code> methods are fundamentally unsafe.</p></blockquote></figure>



<p>Unfortunately, while <code>_performBlockUsingBackingMutableData</code> and its many similar siblings <em>can</em> be made safe, they are (a) all private and (b) not currently enforcing the necessary mutual exclusion.  This could be corrected by Apple in future (although I wouldn&#8217;t hold your breath).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Nominally the <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep/1395583-colorat" data-wpel-link="external" target="_blank" rel="external noopener">colorAt(x:y:)</a></code> method could also be safe, but it currently lacks the same underlying mutual exclusion &#8211; and even if it didn&#8217;t, the performance when using it is atrocious due to the Objective-C method call overhead.  Even utilising <a href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/CodeSpeed/Articles/CriticalCode.html#//apple_ref/doc/uid/20001871-98344" data-wpel-link="external" target="_blank" rel="external noopener">IMP caching</a>, the performance is still terrible compared to directly accessing the bitmap byte buffer.</p>
</div></div>



<h2 class="wp-block-heading">Why does it matter that NSImage is not thread-safe?</h2>



<p>The crux of the problem is that you often have no good choice about it, because <code>NSImage</code> is a <a href="https://forums.swift.org/t/what-does-currency-type-mean/41065" data-wpel-link="external" target="_blank" rel="external noopener">currency type</a> used widely throughout Apple&#8217;s own frameworks, and you often have no control over what thread it&#8217;s created or used on.</p>



<p>For example, even using the very latest Apple APIs such as SwiftUI&#8217;s <code><a href="https://developer.apple.com/documentation/swiftui/view/dropdestination(for:action:istargeted:)" data-wpel-link="external" target="_blank" rel="external noopener">dropDestination(for:action:isTargeted:)</a></code>, you cannot control which thread the <code>NSImage</code>s are created on (it <em>appears</em> to be the main thread, although that API provides no guarantees).</p>



<p>Similarly you have no control over where those images are used if you pass them to 3rd party code &#8211; including Apple&#8217;s.  e.g. <code><a href="https://developer.apple.com/documentation/swiftui/image/init(nsimage:)" data-wpel-link="external" target="_blank" rel="external noopener">Image(nsImage:)</a></code>; <em>possibly</em> that only uses them on the main thread, but it <em>might</em> be pre-rendered the image a separate thread for better performance (to avoid blocking the main thread and causing the app to hang).  In fact it <em>should</em>, in principle.</p>



<p>Loading an image &#8211; actually reading it from a file or URL and decompressing it into a raw bitmap suitable for drawing to the screen &#8211; should never be done on the main thread, because it can take a long time.  A 1 GiB TIFF takes nearly 30 seconds on my iMac Pro, for example (and TIFF uses very lightweight compression, so it&#8217;s a relatively fast-to-read format).  Anything involving the network could take an unbounded amount of time.  Even small files &#8211; like a 20 MiB NEF &#8211; can take seconds to render because they are non-trivial to decode and/or decompress.</p>



<p>So you&#8217;re screwed on multiple levels:  not only can you generally not guarantee what thread <code>NSImage</code> is born on nor used on, you <em>can&#8217;t</em> use it exclusively on the main thread because that will cause a terrible user experience.</p>



<h2 class="wp-block-heading">What can you do?</h2>



<p>In simple terms, the best you can realistically do is try ensure that all modifications to the <code>NSImage</code> (including implicit ones, such as loading a bitmap representation upon first use) happen exclusively in one thread [at a time].  For example, in my experiments, putting a lock around otherwise concurrent calls to <code>bitmapData</code> is enough to prevent any data races.  Although I am mystified as to how I can still let the main thread draw the image concurrently without any apparent problems. 🤔</p>



<p>If you want to play it <em>really</em> safe, you have to create &amp; pre-load each <code>NSImage</code> on a single thread (<em>not</em> the main thread), before ever sending it to another isolation domain.  That means manually reimplementing things like drag and drop of images (because you can no longer work directly with <code>NSImage</code> with any drag &amp; drop APIs, you have to instead use only &#8211; and <em>all</em> &#8211; the things that <em>could potentially be</em> images, like URLs or data blobs, and then translate those to &amp; from <code>NSImage</code>s manually).</p>



<p>Complicating all this is that <code>NSImage</code> is unclear about how <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Images/Images.html#//apple_ref/doc/uid/TP40003290-CH208-SW11" data-wpel-link="external" target="_blank" rel="external noopener">its caching</a> works.  For starters, does this caching apply to the representations or is it a hidden, orthogonal system?  Is it thread-safe?  Etc.</p>



<p>You can supposedly modify the caching behaviour via the <code><a href="https://developer.apple.com/documentation/appkit/nsimage/1519850-cachemode" data-wpel-link="external" target="_blank" rel="external noopener">cacheMode</a></code> property, but in my experience there is no apparent effect no matter what it is set it to (not on the image&#8217;s representations nor on render performance in the GUI).</p>



<p>It&#8217;s a shame that <code>NSImage</code> has been so neglected, and has so many glaring problems.  Over the years Apple have seemingly tried to replace it, introducing new image types <a href="https://developer.apple.com/documentation/coreimage/ciimage/" data-wpel-link="external" target="_blank" rel="external noopener">over</a> and <a href="https://developer.apple.com/documentation/uikit/uiimage/" data-wpel-link="external" target="_blank" rel="external noopener">over</a> again, but all that&#8217;s done is <a href="https://xkcd.com/927/" data-wpel-link="external" target="_blank" rel="external noopener">made everything more complicated</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/nsimage-is-dangerous/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/01/NSImage-thread-unsafety-caught-by-Address-Sanitizer-MallocScribble.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7501</post-id>	</item>
		<item>
		<title>SwiftUI main thread hang detector</title>
		<link>https://wadetregaskis.com/swiftui-main-thread-hang-detector/</link>
					<comments>https://wadetregaskis.com/swiftui-main-thread-hang-detector/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 22 Jan 2024 00:27:53 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7459</guid>

					<description><![CDATA[This is just a little snippet that is quite useful for reporting when your GUI thread (the main thread / actor) hangs for a significant amount of time. There are numerous heavier-weight tools for analysing this sort of thing, but I&#8217;ve found that this simple monitor does what I need most of the time. You&#8230; <a class="read-more-link" href="https://wadetregaskis.com/swiftui-main-thread-hang-detector/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This is just a little snippet that is quite useful for reporting when your GUI thread (the main thread / actor) hangs for a significant amount of time.  There are numerous heavier-weight tools for analysing this sort of thing, but I&#8217;ve found that this simple monitor does what I need most of the time.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="var body: some View {
    SomeRootView {
        …
    }.task {
        let approximateGranularity = Duration.milliseconds(10)
        let threshold = Duration.milliseconds(50)

        let clock = SuspendingClock()
        var lastIteration = clock.now

        while !Task.isCancelled {
            try? await Task.sleep(for: approximateGranularity,
                                  tolerance: approximateGranularity / 2,
                                  clock: clock)

            let now = clock.now

            if now - lastIteration &gt; threshold {
                print(&quot;Main thread hung for &quot;,
                      (now - lastIteration).formatted(.units(width: .wide,
                                                             fractionalPart: .show(length: 2))),
                      &quot;.&quot;,
                      separator: &quot;&quot;)
            }

            lastIteration = now
        }
    }
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> body: some View {</span></span>
<span class="line"><span style="color: #000000">    SomeRootView {</span></span>
<span class="line"><span style="color: #000000">        …</span></span>
<span class="line"><span style="color: #000000">    }.</span><span style="color: #001080">task</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> approximateGranularity = Duration.</span><span style="color: #795E26">milliseconds</span><span style="color: #000000">(</span><span style="color: #098658">10</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> threshold = Duration.</span><span style="color: #795E26">milliseconds</span><span style="color: #000000">(</span><span style="color: #098658">50</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> clock = </span><span style="color: #795E26">SuspendingClock</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">var</span><span style="color: #000000"> lastIteration = clock.</span><span style="color: #001080">now</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">while</span><span style="color: #000000"> !Task.</span><span style="color: #001080">isCancelled</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">try</span><span style="color: #000000">? </span><span style="color: #AF00DB">await</span><span style="color: #000000"> Task.</span><span style="color: #795E26">sleep</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: approximateGranularity,</span></span>
<span class="line"><span style="color: #000000">                                  </span><span style="color: #795E26">tolerance</span><span style="color: #000000">: approximateGranularity / </span><span style="color: #098658">2</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                  </span><span style="color: #795E26">clock</span><span style="color: #000000">: clock)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> now = clock.</span><span style="color: #001080">now</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">if</span><span style="color: #000000"> now - lastIteration &gt; threshold {</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Main thread hung for &quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                      (now - lastIteration).</span><span style="color: #795E26">formatted</span><span style="color: #000000">(.</span><span style="color: #795E26">units</span><span style="color: #000000">(</span><span style="color: #795E26">width</span><span style="color: #000000">: .</span><span style="color: #001080">wide</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                             </span><span style="color: #795E26">fractionalPart</span><span style="color: #000000">: .</span><span style="color: #795E26">show</span><span style="color: #000000">(</span><span style="color: #795E26">length</span><span style="color: #000000">: </span><span style="color: #098658">2</span><span style="color: #000000">))),</span></span>
<span class="line"><span style="color: #000000">                      </span><span style="color: #A31515">&quot;.&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                      </span><span style="color: #795E26">separator</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">            }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            lastIteration = now</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>You can adjust the two parameters &#8211; <code>approximateGranularity</code> and <code>threshold</code> &#8211; to suit your preferences.  The overhead is quite tiny in CPU-usage terms, although be aware that this will cause the main thread to wake up frequently so it may have a noticeable, detrimental energy-usage impact.  I suggest not deploying this to your users.</p>



<p>Perhaps it goes without saying, but a breakpoint set on the <code>print</code> statement enables you to debug deeper into hangs.  Even without that, though, it can be illuminating just to have the log message &#8211; oftentimes you don&#8217;t notice that your app is hanging, because you don&#8217;t happen to be actively interacting with it in that moment.  But your users might.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/swiftui-main-thread-hang-detector/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/01/SPOD.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7459</post-id>	</item>
	</channel>
</rss>
