TIL: SwiftUI List on macOS

Β·

2 min read

I am working on a small macOS app using SwiftUI. I struggled to get List working as I wanted to. TIL that List works better on iOS :)

Out-of-the-boxGoal
StandardFinalVersion

I thought about using ScrollView and a LazyVStack to emulate a list, but I missed out on specific functionality, especially the option to move items in a list.

        List {
            ForEach(items, id: \.id) { entry in
                HStack {
                    Text(entry.text)
                        .lineLimit(2)
                    Spacer()
                    Button {
                        deleteItem(entry)
                    } label: {
                        Image(systemName: "trash")
                    }
                }
            }
            .onMove { indices, destination in
                // TODO: update items array accordingly 
            }
        }

If you do not want to use List, I recommend reading the article from Avi Tsadok for inspiration. He wrote about reordering items in a LazyVStack in iOS apps by replicating UIKit Drag and Drop in SwiftUI. %[avitsadok.medium.com/reorder-items-in-swift..

As you can see in the screenshot above, I had to solve two issues:

  1. Background color of the list
  2. Divider

Background color

Super helpful was this answer to the following Stack Overflow question.

I chose to use this extension to override List's color:

extension NSTableView {
  open override func viewDidMoveToWindow() {
    super.viewDidMoveToWindow()

    backgroundColor = NSColor.clear
    enclosingScrollView!.drawsBackground = false
  }
}

This approach was ok in my case as my app has only one list. An alternative approach is to remove a list's background (without affecting the whole app!) by using Introspect. Introspect allows you to get the underlying UIKit or AppKit element of a SwiftUI view for styling.

Using the extension

Divider

Stack Overflow came to my rescue again.

The divider should really be provided by the List automatically as it does on iOS but unfortunately is not as of macOS 12.3.

The padding around each row in a List seems to be there to account for the selection highlight. So if we want to provide our own Divider but want it to look native while accounting for the highlight padding we would need to offset the Divider like so:

struct ContentView: View {
    @Binding var selection: Set<Item>

    var body: some View {
        List(selection: $selection) {
           SomeView()
              .swiftyListDivider()
        }
    }
}

import SwiftUI
extension View {
    func swiftyListDivider() -> some View {
        background(Divider().offset(y: 4.0), alignment: .bottom)
    }
}

This approach was 'good enough' even though this does not precisely replicate the Divider behavior like other apps.

FinalVersion

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!