DEV Community

loading...
Cover image for A Sidebar in SwiftUI without NavigationView

A Sidebar in SwiftUI without NavigationView

imthath_m profile image Imthath ・4 min read

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.

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.

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 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.

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")
        }

    }
}
Enter fullscreen mode Exit fullscreen mode

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

Default Sidebar

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.

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")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Default Stack View

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.

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

A sidebar in the middle

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

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 on the App Store.

Follow me on Twitter, so that I'll let you when I write more. Feel free to DM if you want to ask/share something.

Discussion

pic
Editor guide