A Sidebar in SwiftUI without NavigationView
Tags: swift, swiftui, ipad, sidebar
Published: 2020-11-02 10:24:09

SwiftUI is really easy and fast for prototyping UI. This opinion has been expressed by many developers in different wordings. Recently, SwiftLee expressed the same in Twitter.

{% twitter 1322862129649770499 %}

I've seen such comments about SwiftUI but this time I decided to add a response, because of the recent challenges (maybe call them limitations 🤔)  I've faced with implementing some not views in SwiftUI. 

{% twitter 1322893159009906688 %}

So, how does SwiftUI fare against the complex requirements of a real world project? At present, it has been in production for only just over a year and we can't find many SwiftUI apps in the App Stores. 

But if there is any good time to build and ship SwiftUI apps, it is right now!

> Disclaimer: Continue only if your minimum target is iOS 13.

Even though most of the SwiftUI features which make it production ready have shipped only with iOS 14, you can build a decent app for iOS 13 which becomes exceptional when the user updates. 

Take the common requirement of a collection view in many apps. You can use the new lazy grids for iOS 14 and drop down to a combination of `VStack` and `HStack` as Paul explains in [this article](https://www.hackingwithswift.com/quick-start/swiftui/how-to-position-views-in-a-grid-using-lazyvgrid-and-lazyhgrid) for iOS 13. Also if your requirement can just be fulfilled using a list, then go for it in iOS 13. 

But if your requirements are complex or you want to tweak the default behavior a little bit, you might find it difficult. Today we'll tackle one such scenario where I want some tweaks to the default behavior and over the coming weeks we'll see many more. 

`SidebarListStyle`  introduced this year, makes it so easy to get the default Sidebar behavior in your app. 

```swift
struct SidebarList: View {
    
    @State var selected: Int? = nil
    
    var body: some View {
        NavigationView {
            List(0...10, id: \.self, selection: $selected) { number in
                NavigationLink(destination: Text("\(number) is selected"), label: { Text("Select \(number)")})
            }
            .listStyle(SidebarListStyle())
            Text("Nothing is selected")
        }
        
    }
}
```

Just with that few lines, you get the default behavior of the Sidebar in a nice Master Detail layout. 

![Default Sidebar](https://dev-to-uploads.s3.amazonaws.com/i/nplxcmqv0stggxml48cm.png)

You get a lot of things out of the box. A Sidebar with spaced out list items, no separators, a nice indication for the selection, a button to toggle the visibility of the Sidebar. If you use the new `Label` for the list item, the images will be in the `accentColor`. So cool, with just a few lines of code!
Now my requirement is to have Sidebar and the detail view, but I don't want them to be inside a `NavigationView`. Sounds simple right? Lets try. 

```swift
struct SplitView: View {
    
    @State var selected: Int? = nil
    
    var body: some View {
        HStack {
            List(0...10, id: \.self, selection: $selected) { number in
                Text("Select \(number)")
            }
            .listStyle(SidebarListStyle())
            detailView
        }
        
    }
    
    @ViewBuilder var detailView: some View {
        if selected == nil {
            Text("Nothing is selected")
        } else {
            Text("\(selected!) is selected")
        }
    }
}
```

![Default Stack View](https://dev-to-uploads.s3.amazonaws.com/i/2n37g2r4er2pplzqm4u4.png)

This is not what I expected to see from the above code. But SwiftUI views react to the environment, you can say, SwiftUI views are dependent on the environment. We got the spaced out list items without separator, but no selection and hence no highlighting when tapped and we need to frame the views in required size. 

I have no clue why removing the `NavigationLink` stops the `List` selection from working. You can get it to work by adding an `onTapGesture` to the `Text`. The `selection` parameter in the list in no longer required. You can fix the Sidebar width by using the `frame` modifier. 

```swift
List(0...10, id: \.self, selection: $selected) { number in
    Text("Select \(number)")
        .onTapGesture {
            selected = number
        }
}
.frame(width: 200)
```

![A sidebar in the middle](https://dev-to-uploads.s3.amazonaws.com/i/lsm4c8u7p2k6fud1yone.png)

Again, this is not what I expected but SwiftUI did its job perfectly. We can fix this easily by adding a `Spacer()` before and after `detailView`. 

The functionality is working properly, so you can tap any item to select it. But there is not visual indication of which item is currently selected. To highlight the selection, add a `background` with `accentCol

Here's the final output.

![The desired custom Sidebar](https://dev-to-uploads.s3.amazonaws.com/i/ltz79zljsbytz93mm5ak.png)

And, below is the code to get that output. Here's the [gist].(https://gist.github.com/mimthath4/4f37773f1f471bdf767ae151685414d3)

We've used a `HStack` and `contentShape` modifier so that tapping anywhere on the list item enables selection. 

This is not the perfect solution, but this works. It took me some mental preparation to share working solutions publicly rather than waiting for the perfect solution all the time.

If you want to see this solution in Production, checkout [Swimbols](https://apps.apple.com/app/id1525226399?platform=ipad) on the App Store.

Follow me [on Twitter](https://twitter.com/intent/follow?screen_name=imthath_m), so that I'll let you when I write more. Feel free to DM if you want to ask/share something.


{% gist https://gist.github.com/mimthath4/4f37773f1f471bdf767ae151685414d3 %}