<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Feedback Loop]]></title><description><![CDATA[App engineering and learnings.]]></description><link>https://vicegax.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!0UVu!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ce5a978-1283-446e-a582-d150b5d7bef2_350x350.png</url><title>Feedback Loop</title><link>https://vicegax.substack.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 18 Apr 2026 01:29:05 GMT</lastBuildDate><atom:link href="https://vicegax.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Vicente Garcia]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[vicegax@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[vicegax@substack.com]]></itunes:email><itunes:name><![CDATA[Vicente Garcia]]></itunes:name></itunes:owner><itunes:author><![CDATA[Vicente Garcia]]></itunes:author><googleplay:owner><![CDATA[vicegax@substack.com]]></googleplay:owner><googleplay:email><![CDATA[vicegax@substack.com]]></googleplay:email><googleplay:author><![CDATA[Vicente Garcia]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[NSViewRepresentable breaks]]></title><description><![CDATA[Behaving differently than UIViewRepresentable]]></description><link>https://vicegax.substack.com/p/nsviewrepresentable-breaks</link><guid isPermaLink="false">https://vicegax.substack.com/p/nsviewrepresentable-breaks</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Mon, 01 Apr 2024 20:08:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!c5RJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Introduction</h1><p>I have found one use case where <a href="https://developer.apple.com/documentation/swiftui/nsviewrepresentable">NSViewRepresentable</a> breaks completely, stops responding and the worst of all: does <strong>not</strong> raise any <strong>error</strong>, <strong>exception</strong>, nor <strong>warning</strong>.</p><p>The exact same use case works as expected using <a href="https://developer.apple.com/documentation/swiftui/uiviewrepresentable">UIViewRepresentable</a>.</p><h1>Context</h1><h2>The app</h2><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UQ4Q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UQ4Q!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png 424w, https://substackcdn.com/image/fetch/$s_!UQ4Q!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png 848w, https://substackcdn.com/image/fetch/$s_!UQ4Q!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png 1272w, https://substackcdn.com/image/fetch/$s_!UQ4Q!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UQ4Q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png" width="114" height="114" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:256,&quot;width&quot;:256,&quot;resizeWidth&quot;:114,&quot;bytes&quot;:44936,&quot;alt&quot;:&quot;Logo for Web app depicting a globe showing the European and African continents&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Logo for Web app depicting a globe showing the European and African continents" title="Logo for Web app depicting a globe showing the European and African continents" srcset="https://substackcdn.com/image/fetch/$s_!UQ4Q!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png 424w, https://substackcdn.com/image/fetch/$s_!UQ4Q!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png 848w, https://substackcdn.com/image/fetch/$s_!UQ4Q!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png 1272w, https://substackcdn.com/image/fetch/$s_!UQ4Q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc02cb5-3f96-42e4-a9fa-ae363b9947a1_256x256.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Web for iOS</figcaption></figure></div><p><strong><a href="https://apps.apple.com/gb/app/Web/id6474901213?platform=iphone">Web</a></strong> is a <strong>Browser</strong> for <code>iOS</code> and <code>macOS</code>, built on top of <code>SwiftUI</code> to empower a better <strong>User Experience</strong>, and higher <strong>Usability</strong>.</p><h2>Frameworks</h2><p><code>SwiftUI</code> is not enough to build a web browser, it has its limitations, the hardest one for being that <code>WebKit</code> is not supported yet, and very likely it will never be, or at least not soon; this is just my uneducated, and potentially inaccurate prediction after having worked with <code>WebKit</code> for several years.</p><h3>WebKit</h3><p>Historically, any web browser for <code>iOS</code> needed to be built using <code>WebKit</code> and <code>WKWebView</code>. Those were the rules, and in my opinion, good ones, as they ensure anyone downloading a browser on <code>iOS</code> would get as much security as possible; but the rules changed <a href="https://developer.apple.com/support/alternative-browser-engines/">recently</a>. In any case, I still believe in <code>WebKit</code> and for me there is no better alternative.</p><p>On <code>macOS</code> there was always the possibility to use a different browser engine, but considering <strong>most</strong> web browsers are <strong>NOT</strong> even on the Mac App Store, and <strong>NONE</strong> of the mainstream ones are, I consider it an statement to publish a browser built using <code>WebKit</code> and supporting <strong>most</strong> of the functionality that a mainstream browser does.</p><h4>A world in itself</h4><p><code>WebKit</code> is infamous for its complexity, there are many things happening there and we, as third party developers, have minimum access to them. We don&#8217;t have control over everything, we can&#8217;t configure out many things, and there are always processes running on the background that we can&#8217;t even observe. Folks could base their PhD dissertation on the art of debugging <code>WKWebView</code>.</p><p><code>WebKit</code> is one of the lowest level frameworks on Apple ecosystems, and also one of the hardest to work with, even using <code>UIKit</code> (on <code>AppKit</code> being even harder).</p><h2>The bridge</h2><p><code>WKWebView</code> is a subclass of <code>UIView/NSView</code>, to use it on <code>SwiftUI</code> we need to do it through <code>UIViewRepresentable</code>, <code>NSViewRepresentable</code> or <a href="https://developer.apple.com/documentation/swiftui/nsviewcontrollerrepresentable">NSViewControllerRepresentable</a>. On this case made sense to use <code>NSViewRepresentable,</code> as we don&#8217;t need a <code>NSViewController</code>.</p><h2>Browser tabs</h2><p>Each tab on the browser is represented by a different <code>WKWebView</code>, displayed using one of the <strong>representable</strong> alternatives. When switching to a different tab the <code>WKWebView</code> is removed from being displayed, but it keeps being retained in memory, it will be added to a representable again when it is time to show it once more.</p><p>This had been working as expected for years, until&#8230;</p><h1>Bug</h1><p>On <code>macOS</code>, while visiting <a href="https://reuters.com">Reuters</a>, then switching tabs, and coming back to Reuters, the screen becomes blank, completely empty. <strong>No error thrown, no exception, no warning</strong>, all the other tabs keep working.</p><h2>Exploration</h2><p>Debugged the issue for a whole week, unable to get any information, or to prevent the  bug from keep happening. Reproducible <code>100%</code> of the time, <strong>but only for that website</strong>.</p><p><code>WKWebView</code> just becomes completely irresponsible, the <code>NSView</code> properties can be accessed, for example <code>frame</code> and <code>isHidden</code>, <code>needsLayout</code>, <code>draw(dirtyRect:)</code>, etc. And nothing seems to work to bring back the web view to work.</p><p><code>evaluateJavaScript(_:completionHandler:)</code> doesn&#8217;t work anymore, <code>reload()</code>,  nor any other of <code>WKWebView</code>&#8217;s methods respond either.</p><h3>Inspecting</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c5RJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c5RJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png 424w, https://substackcdn.com/image/fetch/$s_!c5RJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png 848w, https://substackcdn.com/image/fetch/$s_!c5RJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!c5RJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c5RJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png" width="550" height="431.90184049079755" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1304,&quot;resizeWidth&quot;:550,&quot;bytes&quot;:259437,&quot;alt&quot;:&quot;Safari's web inspector showing reuters.com being completely empty&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Safari's web inspector showing reuters.com being completely empty" title="Safari's web inspector showing reuters.com being completely empty" srcset="https://substackcdn.com/image/fetch/$s_!c5RJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png 424w, https://substackcdn.com/image/fetch/$s_!c5RJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png 848w, https://substackcdn.com/image/fetch/$s_!c5RJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!c5RJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc250366-37ae-489a-a9f8-072b470c478b_1304x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Web Inspector is completely empty</figcaption></figure></div><p>The <code>JavaScript</code> console stops responding, there is <strong>no error or exception shown</strong>, it just goes blank and shows an empty screen with empty content.</p><h1>Hypothesis</h1><p>I have only seen it happening on <code>macOS</code>, on <code>iOS</code> this works as expected.</p><p>It only seems to happen on reuters.com so far, but my theory is that this will happen on other websites, maybe it is due to a specific web library they use, maybe some <code>UI/React</code> framework, maybe some other dependency.</p><p>If it happens in reuters.com I can safely assume it will also happen on many other websites, but I just haven&#8217;t been lucky enough to stumble onto them.</p><p>To make it clear, for sure it is an issue on my browser, and <strong>not on reuters.com</strong></p><h1>Solution</h1><p>Replacing <code>NSViewRepresentable</code> by <code>NSViewControllerRepresentable</code> fixed the issue. I&#8217;m not exactly sure how or why, but this issue is not there anymore.</p><p>To make it compatible, I wrap <code>WKWebView</code> in a basic <code>NSViewController</code>. Such a small price to pay considering the severity of the bug it fixes.</p><h2>Before</h2><h3>Using NSViewRepresentable</h3><pre><code>func makeNSView(context: Context) -&gt; some NSView {
    webView
}</code></pre><h2>After</h2><h3>Using NSViewControllerRepresentable</h3><pre><code>func makeNSViewController(context: Context) -&gt; some NSViewController {
    let controller = NSViewController()
    controller.view = webView
    return controller
}</code></pre><h1>Explanation</h1><h2>Attempting to explain it</h2><p>The inner workings of <code>SwiftUI</code> remain hidden to this day, and we still don&#8217;t understand many things that happen under the hood.</p><p>When we use a variant of the <strong>representables</strong>, either <code>UIViewRepresentable</code>, <code>NSViewRepresentable</code> or <code>NSViewControllerRepresentable</code>, we have even less control and information over what is happening.</p><p>And there is, to my point of view, a discrepancy between <code>UIViewRepresentable</code> and <code>NSViewRepresentable</code>, such that some scenarios that are completely functional on one won&#8217;t necessarily be on the other. Hopefully, when building for <code>macOS</code> we have an alternative.</p><h1>Conclusion</h1><p>While I believe is highly unlikely many developers will face the same issue I was facing, as seems <strong>a</strong> <strong>fairly isolated edge-case</strong>, using <code>WKWebView</code>, and on just very few websites (I have only found <strong>1 so far</strong>), I&#8217;m positive the issue could be manifested on other cases, using different subclasses of <code>NSView</code> or <code>UIView</code>.</p><p>If you ever find yourself struggling to understand why your <code>NSViewRepresentable</code> stopped working without any explanation, try to use a <code>NSViewControllerRepresentable</code> and maybe your issue will be no more.</p><p>Let me know if you have any question.</p>]]></content:encoded></item><item><title><![CDATA[Web Inspector on iOS 16.4]]></title><description><![CDATA[WebKit-based apps need to opt in for Safari Web Inspector]]></description><link>https://vicegax.substack.com/p/web-inspector-on-ios-164</link><guid isPermaLink="false">https://vicegax.substack.com/p/web-inspector-on-ios-164</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Mon, 10 Apr 2023 08:52:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!0o8I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0o8I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0o8I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png 424w, https://substackcdn.com/image/fetch/$s_!0o8I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png 848w, https://substackcdn.com/image/fetch/$s_!0o8I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!0o8I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0o8I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png" width="1456" height="946" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:946,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2912409,&quot;alt&quot;:&quot;Safari on macOS displaying the Develop menu and the option to inspect a WebKit-based iOS app on the iOS Simulator&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Safari on macOS displaying the Develop menu and the option to inspect a WebKit-based iOS app on the iOS Simulator" title="Safari on macOS displaying the Develop menu and the option to inspect a WebKit-based iOS app on the iOS Simulator" srcset="https://substackcdn.com/image/fetch/$s_!0o8I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png 424w, https://substackcdn.com/image/fetch/$s_!0o8I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png 848w, https://substackcdn.com/image/fetch/$s_!0o8I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!0o8I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F872725bf-484a-4058-93b7-7a9d36a08e6b_2560x1664.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Safari on macOS inspecting a WebKit-based iOS app on the Simulator</figcaption></figure></div><p>Starting on <strong>iOS</strong> <code>16.4</code> it is now necessary to opt-in to enable <a href="https://developer.apple.com/library/archive/documentation/AppleApplications/Conceptual/Safari_Developer_Guide/GettingStarted/GettingStarted.html#//apple_ref/doc/uid/TP40007874-CH2-SW20">Safari Web Inspector</a> on a <strong>WebKit</strong>-based app.</p><p>Before this version, it was possible to inspect and use <strong>Safari Developer Tools</strong> in <strong>macOS</strong> on any <strong>iOS</strong> app with <strong>WebKit</strong>, but now we have to turn on the property <a href="https://developer.apple.com/documentation/webkit/wkwebview/4111163-isinspectable">isInspectable</a> in our <code>WKWebView</code>.</p><p>Same behaviour for the <strong>iOS Simulator</strong> and <strong>real devices</strong>. While <strong>Safari</strong> on iOS keeps being inspectable as before.</p><h1>Turn it on</h1><pre><code>wkWebView.isInspectable = true</code></pre><p>This is a great improvement, as we can now control if we want to enable/disable inspecting; a little communication on the change would have been appreciated, though, not to deal with surprises.</p>]]></content:encoded></item><item><title><![CDATA[Removing Search Web from a context menu]]></title><description><![CDATA[Prevent a context menu from ignoring your Default Web Browser App]]></description><link>https://vicegax.substack.com/p/removing-search-web-from-a-context</link><guid isPermaLink="false">https://vicegax.substack.com/p/removing-search-web-from-a-context</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Wed, 15 Mar 2023 13:33:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MnCr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MnCr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MnCr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png 424w, https://substackcdn.com/image/fetch/$s_!MnCr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png 848w, https://substackcdn.com/image/fetch/$s_!MnCr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png 1272w, https://substackcdn.com/image/fetch/$s_!MnCr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MnCr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png" width="528" height="396" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:960,&quot;resizeWidth&quot;:528,&quot;bytes&quot;:354874,&quot;alt&quot;:&quot;iOS context menu on selected text with the option for searching on the web.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="iOS context menu on selected text with the option for searching on the web." title="iOS context menu on selected text with the option for searching on the web." srcset="https://substackcdn.com/image/fetch/$s_!MnCr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png 424w, https://substackcdn.com/image/fetch/$s_!MnCr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png 848w, https://substackcdn.com/image/fetch/$s_!MnCr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png 1272w, https://substackcdn.com/image/fetch/$s_!MnCr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b1b2b7-df4e-4397-ae2c-a2a68124d7bc_960x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A context menu on selected text.</figcaption></figure></div><h1>Context menu</h1><p>iOS presents a context menu when selecting text on some views, like <code>UITextView</code> and <code>WKWebView</code>.</p><p><code>UIResponder</code> protocol allows us to edit the context menu before being presented, with the method <a href="https://developer.apple.com/documentation/uikit/uiresponder/3327317-buildmenu">buildMenu(with:)</a>.</p><h1>Search Web</h1><p>Among the options presented on the context menu, there is one for doing a web search of the selected text; indeed very convenient for most cases.</p><h1>Default browser</h1><p><a href="https://developer.apple.com/documentation/xcode/preparing-your-app-to-be-the-default-browser-or-email-client">Starting on iOS 14</a>, third-party apps can become a default browser, and most web navigation will go through them, this is a &#8220;newish&#8221; feature and still not bulletproof or mature, though.</p><p>While there is a defined way to receive web navigation, i.e. a <code>URL</code> with <code>http</code> or <code>https</code> <code>scheme</code>, there is no <code>scheme</code>, protocol, or deep link defined for web searches, yet.</p><h1>The problem</h1><p><strong>Search Web</strong> option of the context menu does not know how to tell the default browser to do a web search; in fact, it is possible that app doesn&#8217;t have any searching capabilities, as this was not a requirement to become default browser; so this chore will go directly to the only app that for sure can fulfil it: <strong>Safari</strong>.</p><p>But maybe this is not ideal if your app can take care of it.</p><h1>UIMenuBuilder</h1><p>As mentioned above, we can intercept and edit a context menu before being presented, so the natural approach would be to edit the <code>UIMenuBuilder</code> object received on <code>buildMenu(with:)</code>; but wait, <strong>Search Web</strong> is not there&#8230; yet.</p><p><code>UIMenuBuilder</code> contains a collection of menus, each one of them with a collection of <code>UIMenuElement</code>, some of them are a <code>UICommand</code> or a <code>UIAction</code>.</p><p>Interestingly enough, among the menus, we can find the <a href="https://developer.apple.com/documentation/uikit/uimenu/identifier/3281941-lookup">Lookup</a> menu (with the menu identifier <code>lookup</code>), and this has the children <em>Look Up</em>, <em>Find Selection</em> and <em>Translate</em>; if we remove <em>Look Up</em> from the collection, then <strong>Search Web</strong> will not appear on the context menu at all. But that means losing the <strong>Look Up</strong> capabilities, which I like a lot.</p><p>How can we remove <strong>Search Web</strong> without removing <strong>Look Up</strong>?</p><h1>Solution</h1><p>The system edits the context menu before being presented and after giving you the chance to edit it on <code>buildMenu(with:)</code>, if there is a menu with the identifier <code>lookup</code> and with a <code>UICommand</code> that has the selector <code>_define:</code>, then it will insert the <strong>Search Web </strong>option, which is a <code>UIAction</code>.</p><p>More specifically, it will replace the items on the menu with the same items + <strong>Search Web</strong>.</p><p>Let&#8217;s prevent this replacement then.</p><h3>Subclass UIMenu </h3><p>Override <a href="https://developer.apple.com/documentation/uikit/uimenu/3261450-replacingchildren">replacingChildren(_:)</a> with an implementation that does nothing.</p><pre><code>class MyMenu: UIMenu {
    override func replacingChildren(
                     _ newChildren: [UIMenuElement]) -&gt; UIMenu {

        return self
    }
}</code></pre><h3>Replace Look Up</h3><p>Use your <code>UIMenu</code> subclass configured with the properties of the <strong>Look Up </strong>menu.</p><pre><code>override func buildMenu(with builder: UIMenuBuilder) {
    guard let lookUp = builder.menu(for: .lookup) else { return }

    let myMenu = MyMenu(title: lookUp.title,
                        identifier: lookUp.identifier,
                        options: lookUp.options,
                        children: lookUp.children)

    builder.replace(menu: .lookup, with: myMenu)
}</code></pre><h1>Extra points</h1><p>Once <strong>Search Web</strong> is removed, you could add your implementation for web searching with your own <code>UICommand</code> or <code>UIAction</code>; after all, having this on a context menu is quite handy.</p>]]></content:encoded></item><item><title><![CDATA[Dismiss a SwiftUI sheet on macOS]]></title><description><![CDATA[How to dismiss a SwiftUI View presented as a sheet with AppKit on macOS]]></description><link>https://vicegax.substack.com/p/dismiss-a-swiftui-sheet-on-macos</link><guid isPermaLink="false">https://vicegax.substack.com/p/dismiss-a-swiftui-sheet-on-macos</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Thu, 09 Mar 2023 15:41:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!js3h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!js3h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!js3h!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png 424w, https://substackcdn.com/image/fetch/$s_!js3h!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png 848w, https://substackcdn.com/image/fetch/$s_!js3h!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png 1272w, https://substackcdn.com/image/fetch/$s_!js3h!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!js3h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png" width="600" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37436,&quot;alt&quot;:&quot;a hello world displayed as a sheet on macOS.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a hello world displayed as a sheet on macOS." title="a hello world displayed as a sheet on macOS." srcset="https://substackcdn.com/image/fetch/$s_!js3h!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png 424w, https://substackcdn.com/image/fetch/$s_!js3h!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png 848w, https://substackcdn.com/image/fetch/$s_!js3h!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png 1272w, https://substackcdn.com/image/fetch/$s_!js3h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1093d8a6-892d-4bc1-8640-d9db9dc2b011_600x400.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A SwiftUI view presented as a sheet on macOS.</figcaption></figure></div><p>When combining <code>SwiftUI</code> and <code>AppKit</code> you will probably want to participate in the responder chain provided by <code>NSResponder</code> on <strong>macOS</strong>.</p><p><a href="https://developer.apple.com/documentation/swiftui/view/oncommand(_:perform:)">onCommand(_:perform:)</a> view modifier allows us to listen for a <code>Selector</code> and perform an action. In theory, we should be able to use the full power of <code>NSReponder</code> with this, however, depending on the content of your <code>View</code>, you may or may not receive those events, for instance <code>TextField</code> receives them while <code>Toggle</code> doesn&#8217;t, not even after applying <code>focusable(_:)</code> modifier; which makes this solution not reliable at all, especially considering that you might change the <code>View</code> without realising that it will break your responder chain.</p><p>This is even more noticeable when presenting a View as a sheet, for sheets are modal, and there will be no way for the user to dismiss it and continue using your app.</p><p>An alternative is to subclass <a href="https://developer.apple.com/documentation/swiftui/nshostingcontroller">NSHostingController</a> and listen to events with <strong>AppKit</strong>.</p><h1>Example</h1><h2>SwiftUI View</h2><pre><code>struct MyView: View {
    var body: some View {
        Text("hello world")
    }
}</code></pre><h2>Subclassing NSHostingController</h2><p>In this example <code>MyController</code> is listening to <code>cancelOperation(_:)</code> to dismiss itself. This event will be fired, for example, when the user presses <strong>&#9099;</strong> (Escape key)<code>.</code></p><pre><code><code>class MyController&lt;V&gt;: NSHostingController&lt;V&gt; where V : View {
    override func cancelOperation(_ sender: Any?) {
        presentingViewController?.dismiss(self)
    }
}</code></code></pre><h2>Presenting as sheet</h2><pre><code><code>guard let parent = window?.contentViewController else { return }
parent.presentAsSheet(MyController(rootView: MyView()))</code></code></pre><p>With this approach, it is also possible to listen to other <strong>key and mouse events</strong>.</p>]]></content:encoded></item><item><title><![CDATA[Self only as property, subscript or result]]></title><description><![CDATA[Covariant 'Self' or 'Self?' can only appear as the type of a property, subscript or method result; when using reference types.]]></description><link>https://vicegax.substack.com/p/self-only-as-property-subscript-or</link><guid isPermaLink="false">https://vicegax.substack.com/p/self-only-as-property-subscript-or</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Thu, 23 Feb 2023 18:27:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NZ1w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NZ1w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NZ1w!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png 424w, https://substackcdn.com/image/fetch/$s_!NZ1w!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png 848w, https://substackcdn.com/image/fetch/$s_!NZ1w!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png 1272w, https://substackcdn.com/image/fetch/$s_!NZ1w!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NZ1w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png" width="1456" height="387" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:387,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:115400,&quot;alt&quot;:&quot;Error: Cannot convert value of type 'MyClass' to expected argument type 'Self'&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Error: Cannot convert value of type 'MyClass' to expected argument type 'Self'" title="Error: Cannot convert value of type 'MyClass' to expected argument type 'Self'" srcset="https://substackcdn.com/image/fetch/$s_!NZ1w!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png 424w, https://substackcdn.com/image/fetch/$s_!NZ1w!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png 848w, https://substackcdn.com/image/fetch/$s_!NZ1w!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png 1272w, https://substackcdn.com/image/fetch/$s_!NZ1w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea9a36ac-9a6b-4185-8c0a-563b16b03fc1_1768x470.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image 1. Receiving a closure with Self as a parameter</figcaption></figure></div><p>When using <strong>classes</strong> we have the limitation of not being able to use <code>Self</code> as a parameter for a method, even though we can perfectly use it as a <code>return value</code>.</p><p>We have 2 options:</p><ul><li><p>Replace <code>Self</code> by the name of our <strong>class</strong>, as <strong>Xcode</strong> correctly points out in <em>Image 1</em>; however, we loose the power of <strong>Generics</strong>, if this <strong>class</strong> is meant to be subclassed.</p></li><li><p>Using <strong>value types</strong> instead of <strong>reference types</strong>; this limitation is not present on value types as they can&#8217;t be inherited.</p></li></ul><p>It is important to be aware of this limitation as <code>Xcode</code> seems to have a hard time understanding what we are trying to do when using <code>Self</code>, and if our method is complex enough we won&#8217;t be able to see a descriptive and helpful error message. As it is the case in <em>Image 2</em>, seen below.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YytN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YytN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png 424w, https://substackcdn.com/image/fetch/$s_!YytN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png 848w, https://substackcdn.com/image/fetch/$s_!YytN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png 1272w, https://substackcdn.com/image/fetch/$s_!YytN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YytN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png" width="1456" height="743" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:743,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:226959,&quot;alt&quot;:&quot;Xcode cannot understand our code and ceases to work with the message: An internal error occurred.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Xcode cannot understand our code and ceases to work with the message: An internal error occurred." title="Xcode cannot understand our code and ceases to work with the message: An internal error occurred." srcset="https://substackcdn.com/image/fetch/$s_!YytN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png 424w, https://substackcdn.com/image/fetch/$s_!YytN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png 848w, https://substackcdn.com/image/fetch/$s_!YytN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png 1272w, https://substackcdn.com/image/fetch/$s_!YytN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b453c-eec1-4c93-8512-570d853aa1ed_1776x906.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image 2. Using Self as a parameter in a complex method</figcaption></figure></div><p>With those kinds of <strong>undescriptive error messages</strong> it may take us a few hours to realise where we went wrong, I know it because this just happened to me, again.</p><p>It is only when your reference type and your method are simple enough that you receive a helpful error message from <strong>Xcode</strong>, as in <em>Image 3</em>; after this, it all makes sense.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6NRu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6NRu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png 424w, https://substackcdn.com/image/fetch/$s_!6NRu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png 848w, https://substackcdn.com/image/fetch/$s_!6NRu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png 1272w, https://substackcdn.com/image/fetch/$s_!6NRu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6NRu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png" width="1456" height="507" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:507,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:189523,&quot;alt&quot;:&quot;Error: Covariant 'Self' or 'Self?' can only appear as the type of a property, subscript or method result&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Error: Covariant 'Self' or 'Self?' can only appear as the type of a property, subscript or method result" title="Error: Covariant 'Self' or 'Self?' can only appear as the type of a property, subscript or method result" srcset="https://substackcdn.com/image/fetch/$s_!6NRu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png 424w, https://substackcdn.com/image/fetch/$s_!6NRu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png 848w, https://substackcdn.com/image/fetch/$s_!6NRu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png 1272w, https://substackcdn.com/image/fetch/$s_!6NRu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf407386-feaf-4885-86d9-f33e64bb33d5_1774x618.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image 3. Using Self as a method parameter</figcaption></figure></div><p></p>]]></content:encoded></item><item><title><![CDATA[WebKit content blockers not supporting :has()]]></title><description><![CDATA[In-App and Safari content blockers fail with :has() CSS selector.]]></description><link>https://vicegax.substack.com/p/webkit-content-blockers-not-supporting</link><guid isPermaLink="false">https://vicegax.substack.com/p/webkit-content-blockers-not-supporting</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Fri, 17 Feb 2023 12:32:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!XjSJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XjSJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XjSJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!XjSJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!XjSJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!XjSJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XjSJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg" width="960" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:505303,&quot;alt&quot;:&quot;HTML code with a div containing a button and displaying the CSS selector: \&quot;div:has(button)\&quot;.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="HTML code with a div containing a button and displaying the CSS selector: &quot;div:has(button)&quot;." title="HTML code with a div containing a button and displaying the CSS selector: &quot;div:has(button)&quot;." srcset="https://substackcdn.com/image/fetch/$s_!XjSJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!XjSJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!XjSJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!XjSJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5900b613-540d-4e45-b0d2-b3373c8cf684_960x640.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The :has() CSS selector in action</figcaption></figure></div><p>While the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has">:has()</a> selector is supported on <strong>Safari</strong> starting with the version <code>15.4</code>, this appears not to be the case with <a href="https://developer.apple.com/documentation/safariservices/creating_a_content_blocker">content blockers</a> that we can define using <strong>WebKit</strong> both as an app extension for Safari or inside our app embedded to a <code>WKWebView</code> using a <code>WKContentRuleListStore</code>.</p><p>Is important to be aware of this limitation as it will make the entire <code>CSS selector</code> of our content blocker fail (but the rest of the content blocker will still work).</p><h1>:has()</h1><p>This is a functional <code>CSS</code> <em>pseudo-class</em> we can use to build complex CSS selectors nesting a relative selector to another, or in other words, if a parent element contains a child element.</p><p></p>]]></content:encoded></item><item><title><![CDATA[BezelStyle in NSTextField.]]></title><description><![CDATA[Size precision when rendering text.]]></description><link>https://vicegax.substack.com/p/bezelstyle-in-nstextfield</link><guid isPermaLink="false">https://vicegax.substack.com/p/bezelstyle-in-nstextfield</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Mon, 23 Jan 2023 19:20:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!0UVu!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ce5a978-1283-446e-a582-d150b5d7bef2_350x350.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lbJx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lbJx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png 424w, https://substackcdn.com/image/fetch/$s_!lbJx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png 848w, https://substackcdn.com/image/fetch/$s_!lbJx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png 1272w, https://substackcdn.com/image/fetch/$s_!lbJx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lbJx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png" width="516" height="151.76470588235293" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:200,&quot;width&quot;:680,&quot;resizeWidth&quot;:516,&quot;bytes&quot;:18741,&quot;alt&quot;:&quot;A comparison side by side of a cell displaying text, on the left the text overlaps with the bottom border while on the right the same text on the same sized cell fits accordingly.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A comparison side by side of a cell displaying text, on the left the text overlaps with the bottom border while on the right the same text on the same sized cell fits accordingly." title="A comparison side by side of a cell displaying text, on the left the text overlaps with the bottom border while on the right the same text on the same sized cell fits accordingly." srcset="https://substackcdn.com/image/fetch/$s_!lbJx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png 424w, https://substackcdn.com/image/fetch/$s_!lbJx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png 848w, https://substackcdn.com/image/fetch/$s_!lbJx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png 1272w, https://substackcdn.com/image/fetch/$s_!lbJx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F585c0110-1433-46a0-bcdb-830aedbe2769_680x200.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">SquareBezel vs RoundedBezel</figcaption></figure></div><h1>Problem</h1><p>You have some text you want to display and you know exactly how much space you should need to fit this text in, so you create an <code>NSTextField</code> to display it in <strong>macOS</strong> with <strong>AppKit</strong>, and you realise it works as expected for most strings but not for all of them, as a few seem to be not fitting properly by just 1 pixel; try <a href="https://developer.apple.com/documentation/appkit/nstextfield/bezelstyle/roundedbezel">roundedBezel</a> for <code>NSTextField.bezelStyle</code> instead.</p><h1>Bezel</h1><p>Turns out is not enough to configure an <code>NSTextField.isBezeled = false</code> when using it just to display some text without the user being able to edit it. By default the <code>bezelStyle</code> is set to <code>squareBezel</code>, and this seems to be not using the whole <code>width</code> of the <code>frame</code> of the field itself, while <code>roundBezel</code> seems to do it properly.</p><h1>Example</h1><h3>NSParagraphStyle</h3><p>Create a paragraph style so that you can control with precision how the text should be rendered.</p><pre><code>let paragraph = NSMutableParagraphStyle()
paragraph.lineBreakMode = .byWordWrapping
paragraph.lineBreakStrategy = .pushOut
paragraph.allowsDefaultTighteningForTruncation = false
paragraph.tighteningFactorForTruncation = 0
paragraph.usesDefaultHyphenation = false
paragraph.defaultTabInterval = 0
paragraph.hyphenationFactor = 0
paragraph.lineSpacing = 1
paragraph.minimumLineHeight = 1
paragraph.maximumLineHeight = 100</code></pre><h3>NSAttributedString</h3><p>Create an attributed string and configure it with your paragraph style and font.</p><pre><code>let font = NSFont.preferredFont(forTextStyle: .body)

let attributes: [NSAttributedString.Key : Any] = [
            .font: font,
  .paragraphStyle: paragraph]]

let attributedString = NSAttributedString(
                             string: myString, 
                         attributes: attributes)</code></pre><h3>Height</h3><p>Get the <code>height</code> you need to display your <code>attributedString</code> by using <a href="https://developer.apple.com/documentation/foundation/nsattributedstring/1529154-boundingrect">boundingRect(with:options:context:)</a> instance method of <code>NSAttributedString</code>.</p><pre><code>let width = 200.0

let size = CGSize(width: width, 
                 height: .greatestFiniteMagnitude)

let options: [NSString.DrawingOptions] = [
     .usesFontLeading,
     .usesLineFragmentOrigin,
     .truncatesLastVisibleLine,
     .usesDeviceMetrics]

let rect = attributedString
     .boundingRect(with: size
                options: options)

let height = ceil(rect.height)</code></pre><h3>NSTextField</h3><p>Create your <code>NSTextField</code> and put everything together.</p><pre><code>let field = NSTextField()
field.isBezeled = false
field.bezeStyle = .roundedBezel
field.isEditable = false
field.attributedStringValue = attributedString
field.frame.size = .init(width: width, height: height)</code></pre>]]></content:encoded></item><item><title><![CDATA[Progress not publishing all properties]]></title><description><![CDATA[A reminder about Progress updates]]></description><link>https://vicegax.substack.com/p/progress-not-publishing-all-properties</link><guid isPermaLink="false">https://vicegax.substack.com/p/progress-not-publishing-all-properties</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Sun, 22 Jan 2023 13:26:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!50Th!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!50Th!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!50Th!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png 424w, https://substackcdn.com/image/fetch/$s_!50Th!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png 848w, https://substackcdn.com/image/fetch/$s_!50Th!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png 1272w, https://substackcdn.com/image/fetch/$s_!50Th!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!50Th!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png" width="444" height="246.66666666666666" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:500,&quot;width&quot;:900,&quot;resizeWidth&quot;:444,&quot;bytes&quot;:39304,&quot;alt&quot;:&quot;Part of an app displaying the download of a file, with file name, progress indicator, number of bytes downloaded and total bytes.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Part of an app displaying the download of a file, with file name, progress indicator, number of bytes downloaded and total bytes." title="Part of an app displaying the download of a file, with file name, progress indicator, number of bytes downloaded and total bytes." srcset="https://substackcdn.com/image/fetch/$s_!50Th!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png 424w, https://substackcdn.com/image/fetch/$s_!50Th!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png 848w, https://substackcdn.com/image/fetch/$s_!50Th!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png 1272w, https://substackcdn.com/image/fetch/$s_!50Th!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F985c8dda-8255-4c3d-b6fb-a5526b558f89_900x500.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A Progress object in action</figcaption></figure></div><h1>Progress</h1><p><a href="https://developer.apple.com/documentation/foundation/progress">Progress</a> objects are very handy for monitoring or conveying information of a task that might take a while to complete.</p><p>We can use progress objects at our convenience, and sometimes we have no other option but to depend on them; such is the case with <a href="https://developer.apple.com/documentation/webkit/wkdownload">WKDownload</a> objects.</p><p>I have stumbled several times already, over the past couple of years (<code>WKDownload</code> was only added to <strong>WebKit</strong> on <strong>iOS 14.5</strong> and <strong>macOS 11.3</strong>) trying to monitor a downloading in progress and displaying sufficient information on the app; mainly because every time I need to update that part of the code I tend to forget what I learned the previous times, so I write it here hoping next time I can remember.</p><p>You can use <a href="https://developer.apple.com/documentation/combine">Combine</a> and subscribe to the publishers of a Progress object&#8217;s properties, but some of them will publish as expected, some of them will publish constantly, even if there are no changes, and some will never publish at all.</p><h1>Properties</h1><p><code>Progress</code> has many properties available, but not all of them contain information when working with a <code>WKDownload</code>. The ones that I find myself monitoring are:</p><ul><li><p><code>fractionCompleted</code> publishes constantly, generally with changes.</p></li><li><p><code>completedUnitCount</code> publishes constantly, generally with changes.</p></li><li><p><code>totalUnitCount</code> publishes constantly, even without any change.</p></li><li><p><code>localizedDescription</code> publishes constantly, even without any change.</p></li><li><p><code>isFinished</code> publishes only when there are changes.</p></li><li><p><code>fileURL</code> does NOT publish, even if there are changes.</p></li></ul><h1>Example</h1><p>Subscribing to a <code>Progress</code> property with <strong>Combine</strong>, in this case, the progress of a <code>WKDownload</code> object:</p><pre><code>var subscription = wkDownload
    .progress
    .publisher(for: \.fractionCompleted)
    .sink { fraction in
        // fraction is a Double
        // with values from 0 to 1
    }</code></pre>]]></content:encoded></item><item><title><![CDATA[Xcode crash with Source Control]]></title><description><![CDATA[DVTSourceControlGitXPCClient crash on Xcode 14]]></description><link>https://vicegax.substack.com/p/xcode-crash-with-source-control</link><guid isPermaLink="false">https://vicegax.substack.com/p/xcode-crash-with-source-control</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Fri, 20 Jan 2023 14:54:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!sHdM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sHdM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sHdM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png 424w, https://substackcdn.com/image/fetch/$s_!sHdM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png 848w, https://substackcdn.com/image/fetch/$s_!sHdM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png 1272w, https://substackcdn.com/image/fetch/$s_!sHdM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sHdM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png" width="646" height="403.75" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:500,&quot;width&quot;:800,&quot;resizeWidth&quot;:646,&quot;bytes&quot;:51263,&quot;alt&quot;:&quot;git status with the message: nothing to commit, working tree clean. On macOS Terminal&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="git status with the message: nothing to commit, working tree clean. On macOS Terminal" title="git status with the message: nothing to commit, working tree clean. On macOS Terminal" srcset="https://substackcdn.com/image/fetch/$s_!sHdM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png 424w, https://substackcdn.com/image/fetch/$s_!sHdM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png 848w, https://substackcdn.com/image/fetch/$s_!sHdM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png 1272w, https://substackcdn.com/image/fetch/$s_!sHdM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F629a7711-48f3-47b8-ab5c-75d400d86536_800x500.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Working tree clean</figcaption></figure></div><p>A few days ago I deleted a file in my project, then added it again, after that I renamed it, to finally commit it with <strong>Xcode&#8217;s Source Control</strong>.</p><p>Since then and for a few days Xcode was constantly crashing, on a span of every 3-8 minutes; at first I thought it was the app I&#8217;m developing behaving weirdly, then I found out it still happened even without having an app running.</p><p>I couldn&#8217;t understand exactly what the problem was by looking at the logs, but I did notice all of them had the same issue:</p><blockquote><p>Crashed Thread: Dispatch queue: DVTSourceControlGitXPCClient :: Proxy Completion Queue</p></blockquote><p>The part of <strong>DVTSourceControlGitXPCClient</strong> gave me a hint: it had to do with source control, and specifically with <strong>git</strong>.</p><p>I opened the <strong>Terminal</strong>, navigated to my project&#8217;s repository, asked <code>git status</code>, and there it was: that file I <em>deleted</em>, <em>renamed</em> and <em>committed</em> a few days ago was showing as <code>modified</code>.</p><p>Did a staging of all changes, committed them, and since then I haven&#8217;t had any more crashes (so far).</p><p>I have to say is not the first time I notice some limitations on the way <strong>Xcode</strong> interacts with <strong>git</strong>; renaming, and specifically, case-sensitive renames are always an issue.</p><p>In case you are having a similar experience with <strong>Xcode</strong> (14.2 (14C18) in my case), I recommend to try staging everything directly from <strong>Terminal</strong> and see if it fixes your issue.</p><h3>Example:</h3><pre><code>$ cd /myProjectApp
$ git status
$ git add .
$ git commit -m "Cleaning state"</code></pre>]]></content:encoded></item><item><title><![CDATA[Full-screen videos in a browser]]></title><description><![CDATA[isElementFullscreenEnabled on WebKit API]]></description><link>https://vicegax.substack.com/p/full-screen-videos-in-a-browser</link><guid isPermaLink="false">https://vicegax.substack.com/p/full-screen-videos-in-a-browser</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Mon, 16 Jan 2023 19:01:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!mPz4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mPz4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mPz4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png 424w, https://substackcdn.com/image/fetch/$s_!mPz4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png 848w, https://substackcdn.com/image/fetch/$s_!mPz4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png 1272w, https://substackcdn.com/image/fetch/$s_!mPz4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mPz4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png" width="1456" height="728" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1207721,&quot;alt&quot;:&quot;An iPhone playing a video of Simple Minds in full-screen while on landscape orientation.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="An iPhone playing a video of Simple Minds in full-screen while on landscape orientation." title="An iPhone playing a video of Simple Minds in full-screen while on landscape orientation." srcset="https://substackcdn.com/image/fetch/$s_!mPz4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png 424w, https://substackcdn.com/image/fetch/$s_!mPz4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png 848w, https://substackcdn.com/image/fetch/$s_!mPz4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png 1272w, https://substackcdn.com/image/fetch/$s_!mPz4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0beb9ae9-0f21-4297-9684-ef688045110d_2800x1400.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">WKWebView playing a video on full-screen in landscape (app only supports portrait)</figcaption></figure></div><p>Starting on <code>iOS 15.4</code> and <code>macOS 12.3</code> <code>WKPreferences</code> has the new property <a href="https://developer.apple.com/documentation/webkit/wkpreferences/3917769-iselementfullscreenenabled">isElementFullscreenEnabled</a>, part of <code>WKWebViewConfiguration</code>, which enables JavaScript to make elements full-screen in a <code>WKWebView</code>, but it comes with some caveats.</p><h3>macOS</h3><p>On macOS is simple; set it <code>true</code> to play videos on full-screen and make any other HTML element full-screen via JavaScript.</p><pre><code>myWebView.configuration.preferences.isElementFullscreenEnabled = true</code></pre><h3>iOS</h3><p>On iOS, though, setting `<code>isElementFullscreenEnabled = true</code>` disables screen rotation while playing videos on full-screen for apps that only support Portrait Orientation.</p><p>This is important if you have an app that only supports portrait orientation but wants screen rotation when playing a video on full-screen (going back to portrait when the video finishes).</p><p>With <code>isElementFullscreenEnabled = false</code>, you can still play videos on full-screen (at least on YouTube), and rotate videos, but disables making other HTML elements full-screen.</p>]]></content:encoded></item><item><title><![CDATA[SwiftUI List unpredictable behaviour]]></title><description><![CDATA[Broken SwiftUI List when updating state]]></description><link>https://vicegax.substack.com/p/swiftui-list-unpredicted-behaviour</link><guid isPermaLink="false">https://vicegax.substack.com/p/swiftui-list-unpredicted-behaviour</guid><dc:creator><![CDATA[Vicente Garcia]]></dc:creator><pubDate>Sat, 14 Jan 2023 13:46:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rq-X!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been testing an app for about some months now, using it everyday, multiple times during the day, and in that time I saw a quirky little weird behaviour happening on a <code>List</code> maybe 4 times.</p><p>The first times I decided to debug it, but couldn&#8217;t ever manage to reproduce it, so instead tried to convince myself it was somehow fixed by some of the other changes applied (one of those days feeling overly optimistic), as the code base is being updated constantly.</p><p>Last night it happened again and I decided it was time to get to the bottom of this; after lots of testing I managed to find a reproducible path for this behaviour, some more testing and I found how to overcome it, and then I realised it is not the first time I find this exact same challenge, so I decided to write about it, hoping I remember the learnings for next time.</p><p>The unexpected behaviour can be seen on the next picture:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rq-X!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rq-X!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png 424w, https://substackcdn.com/image/fetch/$s_!rq-X!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png 848w, https://substackcdn.com/image/fetch/$s_!rq-X!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png 1272w, https://substackcdn.com/image/fetch/$s_!rq-X!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rq-X!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png" width="608" height="312.7692307692308" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:749,&quot;width&quot;:1456,&quot;resizeWidth&quot;:608,&quot;bytes&quot;:188197,&quot;alt&quot;:&quot;Two iPhones side by side displaying a list in an app. Left shows elements on the list, on the right some elements are duplicated and seem to be off and broken.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Two iPhones side by side displaying a list in an app. Left shows elements on the list, on the right some elements are duplicated and seem to be off and broken." title="Two iPhones side by side displaying a list in an app. Left shows elements on the list, on the right some elements are duplicated and seem to be off and broken." srcset="https://substackcdn.com/image/fetch/$s_!rq-X!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png 424w, https://substackcdn.com/image/fetch/$s_!rq-X!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png 848w, https://substackcdn.com/image/fetch/$s_!rq-X!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png 1272w, https://substackcdn.com/image/fetch/$s_!rq-X!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e7e463-7aa4-495b-b9e1-77d040cdf799_3400x1750.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Left side shows how it should look, right side shows the weirdness</figcaption></figure></div><p>On the left side is possible to appreciate how the <code>List</code> should be displayed. On the right side there is the same <code>List</code> but some elements are duplicated and the List seem to be broken. Notice for example how the section header for Browser is missing on the right.</p><p>The <code>List</code> is built with <code>SwiftUI</code>, and it is using a <code>Published</code> property in an <code>ObservableObject</code>. A extremely simplified representation of the code looks like this:</p><pre><code>// Observable
class MyObservable: ObservableObject {
    @Published var items = [Item]()
}

// List
List {
    FirstSection(observable: observable)
    SecondSection(observable: observable)
}

// FirstSection
@ObservedObject var observable: MyObservable
...
Section {
    ForEach(observable.items) { item in
        ...
    }
}</code></pre><p>The unexpected behaviour was happening after a very specific set of alterations done in a certain order to the <code>Published</code> property <code>items</code>, these alterations involved adding and deleting items, this very specific case was what made reproducing the bug quite a challenge.</p><p>If instead of using the <code>Published</code> property of the <code>ObservedObject</code> for the <code>ForEach</code> in the <code>Section</code> view, a property is used then the <code>List</code> has a better chance of updating itself whenever there are changes:</p><pre><code>// List
List {
    FirstSection(items: observable.items)
...

// FirstSection
let items: [Item]
...
Section {
    ForEach(items) { item in
        ...
    }
}</code></pre><p>The reason for these 2 different behaviours come from the way <code>SwiftUI</code> updates the interface: when using the <code>Published</code> property from the <code>ObservedObject</code> to display items <code>SwiftUI</code> tries to update only those items that change; when using a property <code>SwiftUI</code> invalidates the whole <code>View</code> (or in this case the <code>List</code> <code>Section</code>) when the value of that property changes. <br><br>This comes with some limitations, as it means that the <code>ForEach</code> cannot be created from a <code>Binding</code> and it cannot provide a content closure with individual bindings for each element, <a href="https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-a-list-or-a-foreach-from-a-binding">a great post</a> from Paul Hudson for what I&#8217;m referring to.</p><p>This seems to have fixed the unexpected behaviour, but will keep an eye just in case it happens again.</p><p>Feedback and comments always welcome.</p>]]></content:encoded></item></channel></rss>