<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>hacks &amp;mdash; quigs.blog</title>
    <link>https://wf.quigs.blog/tag:hacks</link>
    <description>WriteFreely preview — text imported from Ghost</description>
    <pubDate>Tue, 25 Aug 2026 21:49:38 +0000</pubDate>
    <item>
      <title>How to Disable OnStar on the Chevy Bolt to Protect Your Privacy</title>
      <link>https://wf.quigs.blog/how-to-disable-onstar-on-the-chevy-bolt-to-protect-your-privacy</link>
      <description>&lt;![CDATA[  ⚠️ 2026 Update: This method is not 100% perfect. Since writing this post I have occasionally gotten OnStar emails from GM reporting things about the car. Isolating radio signals is hard!&#xA;&#xA;Last year I purchased my first modern connected vehicle. As Mozilla has previously reported, modern vehicles capture (and sell) data about your behavior (how you drive, where you live, where you drive to, what your sexual orientation is). As such, I decided to look into the process of disabling OnStar.&#xA;&#xA;Thanks to Imgur I found a way to (reversibly, not permanently) disable OnStar. In short, because OnStar will still report data back to General Motors and their data brokers even if you don&#39;t pay for OnStar (as best I can tell), the only way to truly protect your privacy is to either live in California where you can request to opt out of data collectin via the California Consumer Privacy Act (CCPA) or disable the OnStar hardware. Given that I don&#39;t live in California, I opted for the latter approach.&#xA;&#xA;My thanks to BiggRanger on Imgur for originally documenting this process. I followed their instructions but still ran into some difficulty so I am re-documenting this with more information about the process.&#xA;&#xA;Step 1&#xA;&#xA;Purchase a FAKRA Z Jack Female to SMA Female adapter and an SMA male 50 ohm terminator. This is what will isolate the OnStar modem from the world. Screw the terminator onto the SMA plug. Ensure that it tightened fully to isolate the Bolt from the cellular radio signals.&#xA;&#xA;FAKRA Z Jack adapter with terminator installed.&#xA;&#xA;Step 2&#xA;&#xA;Gain access to the rear of the center console by removing the glove box. Pop open the glove box and remove the damper from the right side. This required pliers to pinch the clip so that it could slide off and took more sliding force than I expected.&#xA;&#xA;The glove box damper and clip.&#xA;&#xA;Squeeze the sides of the glove box inward so that the plastic stops on each side no longer catch, and the glove box cavity opens up completely.&#xA;&#xA;The glove box catch/stopper.&#xA;&#xA;The glove box cavity.&#xA;&#xA;Step 3&#xA;&#xA;Identify the connector. Shine a flash light into the console and find the purple/violet connector. This is the cellular antenna connector (although this guide is for the Bolt, according the FAKRA chart, Purple/Violet is the industry color code for cellular/GSM so this could work in other vehicles too).&#xA;&#xA;The FAKRA Connector Color Code Chart&#xA;&#xA;The inside of the dash behind the center console from outside the glove box opening.&#xA;&#xA;A closer look at the Purple/Violet FAKRA cellular connector.&#xA;&#xA;The cellular connector was closest to the glove box and pretty easy to spot. I had to reach my hand in pretty far to grab it though.&#xA;&#xA;Reach in there, nothing will bite.&#xA;&#xA;Step 4&#xA;&#xA;Unlock the connector and slide it out. This was the hardest part for me. The original Imgur post made it sound like you &#34;just&#34; take a &#34;small&#34; screwdriver and slide out red locking tab so that you can squeeze the connector and pull it off, but sliding the locking tab out of the way took me over an hour. What is small?  &#xA;  &#xA;Turns out &#34;small&#34; means less than 18cm (5 inches) tall, with a very thin flat edge, 4mm (1/8th inch) wide. The electronics screwdriver set I have pictured below was just a little bit too tall to be effective, so I ended up using some &#34;screwdriver pen&#34; thing my wife had laying around.&#xA;&#xA;This is what &#34;small&#34; means. The screwdriver on the right worked for me.&#xA;&#xA;The other issue that gave me trouble was figuring out how far to slide the lock out. I would move it a little and think it had slid out enough, but realize it wasn&#39;t and have to try to slide it out more. The screwdrive slips on the plastic tab a lot, making this challenging. Finally I noticed that there is a &#34;locked&#34; and an &#34;unlocked&#34; indicator dot on the side of the FAKRA jack. Don&#39;t try to pull the connect off until you see the dot all the way into the unlocked position.&#xA;&#xA;Locked position.&#xA;&#xA;Unlocked position.&#xA;&#xA;Step 5&#xA;&#xA;Slide the isolated/terminated connector on. This was super fast and easy to seat and slide on. I felt it lock into place with a subtle click. I left the original connector to the actual cellular antenna handing off to the side. If you ever want to re-enable OnStar, you slide (no need to unlock!) the isolated connector off and slide the original one back on.&#xA;&#xA;Isolated cellular modem, original connector hanging off to the side.&#xA;&#xA;Step 6&#xA;&#xA;Turn on the car and check for the red light. If you see that it&#39;s red you succeeded. You can test that it actually is isolated by attempting to place a call to OnStar. The system &#34;dials&#34; for me but never connected.&#xA;&#xA;Disabled OnStar&#xA;&#xA;Epilogue&#xA;&#xA;OnStar and My Chevrolet are nice for checking the state of charge and charge rates of your Bolt. I discovered that an OBD2 dongle and a reader/writer app can tell you much of the same info. Most of the apps I tried are geared towards gas/diesel cars and required me to import the PIDs for the Bolt and build a custom dashboard. This is not ideal, so I think I might actually build an iOS app to replace the things in My Chevrolet that I care about.&#xA;&#xA;In order to warm or cool the car without having to get in it, you can use the key fob to remote start it by pressing the lock button briefly and then holding the circle arrow icon button for about 5 seconds. This won&#39;t work from miles away, but then the trade-off is that someone miles away can&#39;t violate your privacy or disable your car.&#xA;&#xA;#hacks #modification #cars #ev #privacy]]&gt;</description>
      <content:encoded><![CDATA[<blockquote><p>⚠️ 2026 Update: This method is not 100% perfect. Since writing this post I have occasionally gotten OnStar emails from GM reporting things about the car. Isolating radio signals is hard!</p></blockquote>

<p>Last year I purchased my first modern connected vehicle. As <a href="https://foundation.mozilla.org/en/privacynotincluded/articles/its-official-cars-are-the-worst-product-category-we-have-ever-reviewed-for-privacy/">Mozilla has previously reported</a>, modern vehicles capture (and sell) data about your behavior (how you drive, where you live, where you drive to, what your sexual orientation is). As such, I decided to look into the process of disabling OnStar.</p>

<p>Thanks to <a href="https://imgur.com/gallery/n00QKnH">Imgur</a> I found a way to (reversibly, not permanently) disable OnStar. In short, because OnStar will still report data back to General Motors and their data brokers even if you don&#39;t pay for OnStar (as best I can tell), the only way to truly protect your privacy is to either live in California where you can request to opt out of data collectin via the <a href="https://oag.ca.gov/privacy/ccpa">California Consumer Privacy Act (CCPA)</a> or disable the OnStar hardware. Given that I don&#39;t live in California, I opted for the latter approach.</p>

<p>My thanks to <a href="https://imgur.com/user/BiggRanger">BiggRanger</a> on Imgur for originally documenting this process. I followed their instructions but still ran into some difficulty so I am re-documenting this with more information about the process.</p>

<h2 id="step-1">Step 1</h2>

<p>Purchase a <a href="https://connectorsupplier.com/meet-the-connector-fakra-connectors/">FAKRA</a> Z Jack Female to SMA Female <a href="https://www.amazon.com/Bingfu-Navigation-Satellite-Telematics-Bluetooth/dp/B07PFLZ4HF?th=1">adapter</a> and an SMA male 50 ohm <a href="https://www.amazon.com/gp/product/B08MXLN9QX/ref=ppx_od_dt_b_asin_title_s00?ie=UTF8&amp;psc=1">terminator</a>. This is what will isolate the OnStar modem from the world. Screw the terminator onto the SMA plug. Ensure that it tightened fully to isolate the Bolt from the cellular radio signals.</p>

<p><img src="https://quigs.blog/content/images/2026/04/IMG_5525-Large.jpeg" alt=""></p>

<p><em>FAKRA Z Jack adapter with terminator installed.</em></p>

<h2 id="step-2">Step 2</h2>

<p>Gain access to the rear of the center console by removing the glove box. Pop open the glove box and remove the damper from the right side. This required pliers to pinch the clip so that it could slide off and took more sliding force than I expected.</p>

<p><img src="https://quigs.blog/content/images/2026/04/IMG_5526-Large.jpeg" alt=""></p>

<p><em>The glove box damper and clip.</em></p>

<p>Squeeze the sides of the glove box inward so that the plastic stops on each side no longer catch, and the glove box cavity opens up completely.</p>

<p><img src="https://quigs.blog/content/images/2026/04/IMG_5528-Large.jpeg" alt=""></p>

<p><em>The glove box catch/stopper.</em>
<img src="https://quigs.blog/content/images/2026/04/IMG_5531-Large.jpeg" alt=""></p>

<p><em>The glove box cavity.</em></p>

<h2 id="step-3">Step 3</h2>

<p>Identify the connector. Shine a flash light into the console and find the purple/violet connector. This is the cellular antenna connector (although this guide is for the Bolt, according the FAKRA chart, Purple/Violet is the industry color code for cellular/GSM so this could work in other vehicles too).</p>

<p><img src="https://quigs.blog/content/images/2026/04/fakra_color_codes.png" alt=""></p>

<p><em>The FAKRA Connector Color Code Chart</em>
<img src="https://quigs.blog/content/images/2026/04/IMG_5532-Large.jpeg" alt=""></p>

<p><em>The inside of the dash behind the center console from outside the glove box opening.</em>
<img src="https://quigs.blog/content/images/2026/04/IMG_5537-Large.jpeg" alt=""></p>

<p><em>A closer look at the Purple/Violet FAKRA cellular connector.</em></p>

<p>The cellular connector was closest to the glove box and pretty easy to spot. I had to reach my hand in pretty far to grab it though.</p>

<p><img src="https://quigs.blog/content/images/2026/04/IMG_5536-Large-2.jpeg" alt=""></p>

<p><em>Reach in there, nothing will bite.</em></p>

<h2 id="step-4">Step 4</h2>

<p>Unlock the connector and slide it out. This was the hardest part for me. The original Imgur post made it sound like you “just” take a “small” screwdriver and slide out red locking tab so that you can squeeze the connector and pull it off, but sliding the locking tab out of the way took me over an hour. What is small?</p>

<p>Turns out “small” means less than 18cm (5 inches) tall, with a very thin flat edge, 4mm (1/8th inch) wide. The electronics screwdriver set I have pictured below was just a little bit too tall to be effective, so I ended up using some “screwdriver pen” thing my wife had laying around.</p>

<p><img src="https://quigs.blog/content/images/2026/04/IMG_5564.jpeg" alt=""></p>

<p><em>This is what “small” means. The screwdriver on the right worked for me.</em></p>

<p>The other issue that gave me trouble was figuring out how far to slide the lock out. I would move it a little and think it had slid out enough, but realize it wasn&#39;t and have to try to slide it out more. The screwdrive slips on the plastic tab a lot, making this challenging. Finally I noticed that there is a “locked” and an “unlocked” indicator dot on the side of the FAKRA jack. Don&#39;t try to pull the connect off until you see the dot all the way into the unlocked position.</p>

<p><img src="https://quigs.blog/content/images/2026/04/IMG_5543-Large-1.jpeg" alt=""></p>

<p><em>Locked position.</em>
<img src="https://quigs.blog/content/images/2026/04/IMG_5548-Large.jpeg" alt=""></p>

<p><em>Unlocked position.</em></p>

<h2 id="step-5">Step 5</h2>

<p>Slide the isolated/terminated connector on. This was super fast and easy to seat and slide on. I felt it lock into place with a subtle click. I left the original connector to the actual cellular antenna handing off to the side. If you ever want to re-enable OnStar, you slide (no need to unlock!) the isolated connector off and slide the original one back on.</p>

<p><img src="https://quigs.blog/content/images/2026/04/IMG_5555-Large-2.jpeg" alt=""></p>

<p><em>Isolated cellular modem, original connector hanging off to the side.</em></p>

<h2 id="step-6">Step 6</h2>

<p>Turn on the car and check for the red light. If you see that it&#39;s red you succeeded. You can test that it actually is isolated by attempting to place a call to OnStar. The system “dials” for me but never connected.</p>

<p><img src="https://quigs.blog/content/images/2026/04/IMG_5556-Large-1.jpeg" alt=""></p>

<p><em>Disabled OnStar</em></p>

<h2 id="epilogue">Epilogue</h2>

<p>OnStar and My Chevrolet are nice for checking the state of charge and charge rates of your Bolt. I discovered that an OBD2 dongle and a reader/writer app can tell you much of the same info. Most of the apps I tried are geared towards gas/diesel cars and required me to import <a href="https://allev.info/boltpids/">the PIDs for the Bolt</a> and build a custom dashboard. This is not ideal, so I think I might actually build an iOS app to replace the things in My Chevrolet that I care about.</p>

<p>In order to warm or cool the car without having to get in it, you can use the key fob to remote start it by pressing the lock button briefly and then holding the circle arrow icon button for about 5 seconds. This won&#39;t work from miles away, but then the trade-off is that someone miles away can&#39;t violate your privacy or disable your car.</p>

<p><a href="https://wf.quigs.blog/tag:hacks" class="hashtag"><span>#</span><span class="p-category">hacks</span></a> <a href="https://wf.quigs.blog/tag:modification" class="hashtag"><span>#</span><span class="p-category">modification</span></a> <a href="https://wf.quigs.blog/tag:cars" class="hashtag"><span>#</span><span class="p-category">cars</span></a> <a href="https://wf.quigs.blog/tag:ev" class="hashtag"><span>#</span><span class="p-category">ev</span></a> <a href="https://wf.quigs.blog/tag:privacy" class="hashtag"><span>#</span><span class="p-category">privacy</span></a></p>
]]></content:encoded>
      <guid>https://wf.quigs.blog/how-to-disable-onstar-on-the-chevy-bolt-to-protect-your-privacy</guid>
      <pubDate>Tue, 05 Mar 2024 01:48:29 +0000</pubDate>
    </item>
    <item>
      <title>NavigationSplitView limitations: Navigation without List views (SwiftUI 4)</title>
      <link>https://wf.quigs.blog/navigationsplitview-limitations-navigation-without-list-views-swiftui-4</link>
      <description>&lt;![CDATA[Have you noticed that all the examples on Apple&#39;s website, on Medium, and by prominent SwiftUI bloggers only use List views for the sidebar(s) and don&#39;t have a NavigationStack in the final detail view that they push additional content onto? There&#39;s a reason for that, and unfortunately it&#39;s not a pleasant one.&#xA;&#xA;If you&#39;re looking to build a seemingly simple split-view app with a 1-depth sidebar and an n-depth detail view, using List in the sidebar view is a fine choice. But if you do not want the cookie-cutter look and feel List is not your friend. Unfortunately, NavigationSplitView really, really wants you to use List and if you don&#39;t use one then don&#39;t you dare try to programmatically push more content onto the detail view&#39;s navigation stack.&#xA;&#xA;This split view demo from WWDC looks great, but what if your side-bar UI would look better if it were layed out as a grid?&#xA;&#xA;At first glance, dropping the sidebar List view seems to work fine. I will be modifying Majid Jabrayilov&#39;s excellent split view code example to demonstrate the limitations (shortcomings? Bugs?) of SwiftUI 4&#39;s NavigationSplitView. First let&#39;s keep the List-backed sidebar and try to programmatically navigate two levels deep in the detail view:&#xA;&#xA;struct ContentView: View {&#xA;    @State private var selectedItem: String?&#xA;    @State private var navPath = NavigationPath()&#xA;    &#xA;    @State private var folders = [&#xA;        &#34;All&#34;: [&#xA;            &#34;Item1&#34;,&#xA;            &#34;Item2&#34;&#xA;        ],&#xA;        &#34;Favorites&#34;: [&#xA;            &#34;Item2&#34;&#xA;        ]&#xA;    ]&#xA;    &#xA;    var body: some View {&#xA;        NavigationSplitView {&#xA;            List(selection: $selectedItem) {&#xA;                ForEach(folders[&#34;All&#34;, default: []], id: \.self) { item in&#xA;                    NavigationLink(value: item) {&#xA;                        Text(verbatim: item)&#xA;                    }&#xA;                }&#xA;            }&#xA;            .navigationTitle(&#34;All&#34;)&#xA;        } detail: {&#xA;            NavigationStack(path: $navPath) {&#xA;                VStack {&#xA;                    if let selectedItem {&#xA;                        Button(&#34;Button \(selectedItem)&#34;) {&#xA;                            navPath.append(selectedItem)&#xA;                        }&#xA;                    } else {&#xA;                        Text(&#34;Choose an item from the content&#34;)&#xA;                    }&#xA;                }&#xA;                .navigationDestination(for: String.self) { text in&#xA;                    Text(verbatim: text)&#xA;                        .navigationTitle(text)&#xA;                }&#xA;            }&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;This works great, and if you select the &#34;Button ItemN&#34; button, you will see it programmatically pushes a view onto the detail view&#39;s stack.&#xA;&#xA;A 2-level deep detail view on a NavigationSplitView running on an iPhone/compact horizontal size class.&#xA;&#xA;Now let&#39;s get rid of that bog-standard List view in the sidebar and replace it with a horizontal grid:&#xA;&#xA;struct ContentView: View {&#xA;    @State private var selectedItem: String?&#xA;    @State private var navPath = NavigationPath()&#xA;    &#xA;    @State private var folders = [&#xA;        &#34;All&#34;: [&#xA;            &#34;Item1&#34;,&#xA;            &#34;Item2&#34;&#xA;        ],&#xA;        &#34;Favorites&#34;: [&#xA;            &#34;Item2&#34;&#xA;        ]&#xA;    ]&#xA;    &#xA;    let rows = [GridItem(.fixed(30))]&#xA;    &#xA;    var body: some View {&#xA;        NavigationSplitView {&#xA;            ScrollView {&#xA;                LazyHGrid(rows: rows) {&#xA;                    ForEach(folders[&#34;All&#34;, default: []], id: \.self) { item in&#xA;                        NavigationLink(value: item) {&#xA;                            Text(verbatim: item)&#xA;                        }&#xA;                    }&#xA;                }&#xA;            }&#xA;            .navigationTitle(&#34;All&#34;)&#xA;        } detail: {&#xA;            NavigationStack(path: $navPath) {&#xA;                VStack {&#xA;                    if let selectedItem {&#xA;                        Button(&#34;Button \(selectedItem)&#34;) {&#xA;                            navPath.append(selectedItem)&#xA;                        }&#xA;                    } else {&#xA;                        Text(&#34;Choose an item from the content&#34;)&#xA;                    }&#xA;                }&#xA;                .navigationDestination(for: String.self) { text in&#xA;                    Text(verbatim: text)&#xA;                        .navigationTitle(text)&#xA;                }&#xA;            }&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;It looks great (🙃). Let&#39;s test and then ship it!&#xA;&#xA;A svelte grid layout in the side bar instead of a List . 🔥!&#xA;&#xA;As soon as we select an item though, we get a runtime error:&#xA;&#xA;  A NavigationLink is presenting a value of type “String” but there is no matching navigationDestination declaration visible from the location of the link. The link cannot be activated.&#xA;&#xA;Ugh. Let&#39;s fix that by providing the sidebar view with a .navigationDestination. We will move the detail view to it&#39;s own struct so it can be re-used.&#xA;&#xA;struct ContentView: View {&#xA;    @State private var selectedItem: String?&#xA;    @State private var navPath = NavigationPath()&#xA;    &#xA;    @State private var folders = [&#xA;        &#34;All&#34;: [&#xA;            &#34;Item1&#34;,&#xA;            &#34;Item2&#34;&#xA;        ],&#xA;        &#34;Favorites&#34;: [&#xA;            &#34;Item2&#34;&#xA;        ]&#xA;    ]&#xA;    &#xA;    let rows = [GridItem(.fixed(30))]&#xA;    &#xA;    var body: some View {&#xA;        NavigationSplitView {&#xA;            ScrollView {&#xA;                LazyHGrid(rows: rows) {&#xA;                    ForEach(folders[&#34;All&#34;, default: []], id: \.self) { item in&#xA;                        NavigationLink(value: item) {&#xA;                            Text(verbatim: item)&#xA;                        }&#xA;                    }&#xA;                }&#xA;            }&#xA;            .navigationDestination(for: String.self) { selection in&#xA;                DetailView(navPath: $navPath, selectedItem: selection)&#xA;            }&#xA;            .navigationTitle(&#34;All&#34;)&#xA;        } detail: {&#xA;            NavigationStack(path: $navPath) {&#xA;                DetailView(navPath: $navPath, selectedItem: selectedItem)&#xA;            }&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;struct DetailView: View {&#xA;    @Binding var navPath: NavigationPath&#xA;    let selectedItem: String?&#xA;    var body: some View {&#xA;        VStack {&#xA;            if let selectedItem {&#xA;                Button(&#34;Button \(selectedItem)&#34;) {&#xA;                    navPath.append(selectedItem)&#xA;                }&#xA;            } else {&#xA;                Text(&#34;Choose an item from the content&#34;)&#xA;            }&#xA;        }&#xA;        .navigationDestination(for: String.self) { text in&#xA;            Text(verbatim: text)&#xA;                .navigationTitle(text)&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;When we run this we get to the first level of the detail view, but when we select the &#34;Button ItemN&#34; programmatic navigation button... nothing happens. And this is where it all falls apart.&#xA;&#xA;As of this writing, Jan 1, 2023, on Xcode 14.2 there is no way (that I&#39;ve found) to programmatically navigate to the second level of the detail view. SwiftUI just doesn&#39;t do anything. There are no errors or warnings in the debug console. There is just silence and no navigation.&#xA;&#xA;In fact, the only way to get the detail view to push a second level deep without a List-backed sidebar is to get rid of programmatic navigation on the detail view altogether and replace the button with a NavigationLink:&#xA;&#xA;struct DetailView: View {&#xA;    @Binding var navPath: NavigationPath&#xA;    let selectedItem: String?&#xA;    var body: some View {&#xA;        VStack {&#xA;            if let selectedItem {&#xA;                NavigationLink(value: selectedItem) {&#xA;                    Text(&#34;NavigationLink \(selectedItem)&#34;)&#xA;                }&#xA;            } else {&#xA;                Text(&#34;Choose an item from the content&#34;)&#xA;            }&#xA;        }&#xA;        .navigationDestination(for: String.self) { text in&#xA;            Text(verbatim: text)&#xA;                .navigationTitle(text)&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;A navigation split view with a grid-based &#34;sidebar&#34; view and a NavigationLink in the first detail view.&#xA;&#xA;Great! It works now--but only if your user selects the navigation link directly. As with many things in SwiftUI over the last 4 years, this feels like such simple edge case, and yet there&#39;s nothing that can be done about it. Our options are either:&#xA;&#xA;Host a UISplitViewController in our SwiftUI app and manually handle navigation.&#xA;Don&#39;t programmatically navigate after the first detail view.&#xA;Use a List for the sidebar view and deal with the visual constraints of a List.&#xA;&#xA;Hopefully this is something the SwiftUI team at Apple can resolve soon. iOS 16 brought a huge quality-of-life improvement to navigation on SwiftUI. It would be nice to see this fixed before iOS 17, but until then if you require a unique-looking UI that navigates sanely, don&#39;t let your UIKit skills atrophy just yet. SwiftUI continues to be &#34;close&#34; but not quite &#34;there&#34; yet for another year.&#xA;&#xA;#howto #programming #swiftui #swiftui4 #hacks #workarounds]]&gt;</description>
      <content:encoded><![CDATA[<p>Have you noticed that all the examples on <a href="https://developer.apple.com/documentation/swiftui/navigationsplitview">Apple&#39;s website</a>, on Medium, and by <a href="https://swiftwithmajid.com/2022/10/18/mastering-navigationsplitview-in-swiftui/">prominent</a> <a href="https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-a-two-column-or-three-column-layout-with-navigationsplitview">SwiftUI</a> bloggers only use <code>List</code> views for the sidebar(s) and don&#39;t have a <code>NavigationStack</code> in the final detail view that they push additional content onto? There&#39;s a reason for that, and unfortunately it&#39;s not a pleasant one.</p>

<p>If you&#39;re looking to build a seemingly simple split-view app with a 1-depth sidebar and an n-depth detail view, using <code>List</code> in the sidebar view is a fine choice. But if you do not want the cookie-cutter look and feel <code>List</code> is not your friend. Unfortunately, <code>NavigationSplitView</code> <em>really, really</em> wants you to use <code>List</code> and if you don&#39;t use one then don&#39;t you dare try to programmatically push more content onto the detail view&#39;s navigation stack.</p>

<p><img src="https://quigs.blog/content/images/2022/12/navigation_split_view-1.png" alt=""></p>

<p><em>This split view demo from WWDC looks great, but what if your side-bar UI would look better if it were layed out as a grid?</em></p>

<p>At first glance, dropping the sidebar <code>List</code> view seems to work fine. I will be modifying Majid Jabrayilov&#39;s <a href="https://swiftwithmajid.com/2022/10/18/mastering-navigationsplitview-in-swiftui/">excellent split view code example</a> to demonstrate the limitations (shortcomings? Bugs?) of SwiftUI 4&#39;s <code>NavigationSplitView</code>. First let&#39;s keep the <code>List</code>-backed sidebar and try to programmatically navigate two levels deep in the detail view:</p>

<pre><code class="language-swift">struct ContentView: View {
    @State private var selectedItem: String?
    @State private var navPath = NavigationPath()
    
    @State private var folders = [
        &#34;All&#34;: [
            &#34;Item1&#34;,
            &#34;Item2&#34;
        ],
        &#34;Favorites&#34;: [
            &#34;Item2&#34;
        ]
    ]
    
    var body: some View {
        NavigationSplitView {
            List(selection: $selectedItem) {
                ForEach(folders[&#34;All&#34;, default: []], id: \.self) { item in
                    NavigationLink(value: item) {
                        Text(verbatim: item)
                    }
                }
            }
            .navigationTitle(&#34;All&#34;)
        } detail: {
            NavigationStack(path: $navPath) {
                VStack {
                    if let selectedItem {
                        Button(&#34;Button \(selectedItem)&#34;) {
                            navPath.append(selectedItem)
                        }
                    } else {
                        Text(&#34;Choose an item from the content&#34;)
                    }
                }
                .navigationDestination(for: String.self) { text in
                    Text(verbatim: text)
                        .navigationTitle(text)
                }
            }
        }
    }
}
</code></pre>

<p>This works great, and if you select the “Button ItemN” button, you will see it programmatically pushes a view onto the detail view&#39;s stack.</p>

<p><img src="https://quigs.blog/content/images/2023/01/NavigationSplitView-with-List-Sidebar-View.JPG" alt=""></p>

<p><em>A 2-level deep detail view on a NavigationSplitView running on an iPhone/compact horizontal size class.</em></p>

<p>Now let&#39;s get rid of that bog-standard <code>List</code> view in the sidebar and replace it with a horizontal grid:</p>

<pre><code class="language-swift">struct ContentView: View {
    @State private var selectedItem: String?
    @State private var navPath = NavigationPath()
    
    @State private var folders = [
        &#34;All&#34;: [
            &#34;Item1&#34;,
            &#34;Item2&#34;
        ],
        &#34;Favorites&#34;: [
            &#34;Item2&#34;
        ]
    ]
    
    let rows = [GridItem(.fixed(30))]
    
    var body: some View {
        NavigationSplitView {
            ScrollView {
                LazyHGrid(rows: rows) {
                    ForEach(folders[&#34;All&#34;, default: []], id: \.self) { item in
                        NavigationLink(value: item) {
                            Text(verbatim: item)
                        }
                    }
                }
            }
            .navigationTitle(&#34;All&#34;)
        } detail: {
            NavigationStack(path: $navPath) {
                VStack {
                    if let selectedItem {
                        Button(&#34;Button \(selectedItem)&#34;) {
                            navPath.append(selectedItem)
                        }
                    } else {
                        Text(&#34;Choose an item from the content&#34;)
                    }
                }
                .navigationDestination(for: String.self) { text in
                    Text(verbatim: text)
                        .navigationTitle(text)
                }
            }
        }
    }
}
</code></pre>

<p>It looks great (🙃). Let&#39;s test and then ship it!</p>

<p><img src="https://quigs.blog/content/images/2023/01/GridView-Layout-3.PNG" alt=""></p>

<p><em>A svelte grid layout in the side bar instead of a List . 🔥!</em></p>

<p>As soon as we select an item though, we get a runtime error:</p>

<blockquote><p>A NavigationLink is presenting a value of type “String” but there is no matching navigationDestination declaration visible from the location of the link. The link cannot be activated.</p></blockquote>

<p>Ugh. Let&#39;s fix that by providing the sidebar view with a <code>.navigationDestination</code>. We will move the detail view to it&#39;s own struct so it can be re-used.</p>

<pre><code class="language-swift">struct ContentView: View {
    @State private var selectedItem: String?
    @State private var navPath = NavigationPath()
    
    @State private var folders = [
        &#34;All&#34;: [
            &#34;Item1&#34;,
            &#34;Item2&#34;
        ],
        &#34;Favorites&#34;: [
            &#34;Item2&#34;
        ]
    ]
    
    let rows = [GridItem(.fixed(30))]
    
    var body: some View {
        NavigationSplitView {
            ScrollView {
                LazyHGrid(rows: rows) {
                    ForEach(folders[&#34;All&#34;, default: []], id: \.self) { item in
                        NavigationLink(value: item) {
                            Text(verbatim: item)
                        }
                    }
                }
            }
            .navigationDestination(for: String.self) { selection in
                DetailView(navPath: $navPath, selectedItem: selection)
            }
            .navigationTitle(&#34;All&#34;)
        } detail: {
            NavigationStack(path: $navPath) {
                DetailView(navPath: $navPath, selectedItem: selectedItem)
            }
        }
    }
}

struct DetailView: View {
    @Binding var navPath: NavigationPath
    let selectedItem: String?
    var body: some View {
        VStack {
            if let selectedItem {
                Button(&#34;Button \(selectedItem)&#34;) {
                    navPath.append(selectedItem)
                }
            } else {
                Text(&#34;Choose an item from the content&#34;)
            }
        }
        .navigationDestination(for: String.self) { text in
            Text(verbatim: text)
                .navigationTitle(text)
        }
    }
}
</code></pre>

<p>When we run this we get to the first level of the detail view, but when we select the “Button ItemN” programmatic navigation button... nothing happens. And this is where it all falls apart.</p>

<p>As of this writing, Jan 1, 2023, on Xcode 14.2 there is no way (that I&#39;ve found) to programmatically navigate to the second level of the detail view. SwiftUI just doesn&#39;t do anything. There are no errors or warnings in the debug console. There is just silence and no navigation.</p>

<p>In fact, the only way to get the detail view to push a second level deep without a <code>List</code>-backed sidebar is to get rid of programmatic navigation on the detail view altogether and replace the button with a <code>NavigationLink</code>:</p>

<pre><code class="language-swift">struct DetailView: View {
    @Binding var navPath: NavigationPath
    let selectedItem: String?
    var body: some View {
        VStack {
            if let selectedItem {
                NavigationLink(value: selectedItem) {
                    Text(&#34;NavigationLink \(selectedItem)&#34;)
                }
            } else {
                Text(&#34;Choose an item from the content&#34;)
            }
        }
        .navigationDestination(for: String.self) { text in
            Text(verbatim: text)
                .navigationTitle(text)
        }
    }
}
</code></pre>

<p><img src="https://quigs.blog/content/images/2023/01/NavigationSplitView-with-Grid-Sidebar-View-1.JPG" alt=""></p>

<p><em>A navigation split view with a grid-based “sidebar” view and a NavigationLink in the first detail view.</em></p>

<p>Great! It works now—but only if your user selects the navigation link directly. As with many things in SwiftUI over the last 4 years, this feels like such simple edge case, and yet there&#39;s nothing that can be done about it. Our options are either:</p>
<ol><li>Host a <code>UISplitViewController</code> in our SwiftUI app and manually handle navigation.</li>
<li>Don&#39;t programmatically navigate after the first detail view.</li>
<li>Use a <code>List</code> for the sidebar view and deal with the visual constraints of a <code>List</code>.</li></ol>

<p>Hopefully this is something the SwiftUI team at Apple can resolve soon. iOS 16 brought a huge quality-of-life improvement to navigation on SwiftUI. It would be nice to see this fixed before iOS 17, but until then if you require a unique-looking UI that navigates sanely, don&#39;t let your <code>UIKit</code> skills atrophy just yet. SwiftUI continues to be “close” but not quite “there” yet for another year.</p>

<p><a href="https://wf.quigs.blog/tag:howto" class="hashtag"><span>#</span><span class="p-category">howto</span></a> <a href="https://wf.quigs.blog/tag:programming" class="hashtag"><span>#</span><span class="p-category">programming</span></a> <a href="https://wf.quigs.blog/tag:swiftui" class="hashtag"><span>#</span><span class="p-category">swiftui</span></a> <a href="https://wf.quigs.blog/tag:swiftui4" class="hashtag"><span>#</span><span class="p-category">swiftui4</span></a> <a href="https://wf.quigs.blog/tag:hacks" class="hashtag"><span>#</span><span class="p-category">hacks</span></a> <a href="https://wf.quigs.blog/tag:workarounds" class="hashtag"><span>#</span><span class="p-category">workarounds</span></a></p>
]]></content:encoded>
      <guid>https://wf.quigs.blog/navigationsplitview-limitations-navigation-without-list-views-swiftui-4</guid>
      <pubDate>Mon, 02 Jan 2023 00:19:56 +0000</pubDate>
    </item>
  </channel>
</rss>