Search code examples
macosswiftuidrag-and-dropsidebar

How to get the drop target background effect in sidebars on macOS using SwiftUI?


I have a SwiftUI List that's a sidebar on macOS. For its items I have added the dropDesternation modifier like this:

.dropDestination(for: URL.self) { urls, _ in
      for url in urls {
          //... adding urls to destination
      }
}

return true
} isTargeted: { inDropArea in
      if inDropArea {
          highlightedItem = item
      } else {
          highlightedItem = nil
    }
}
       

By default if the cursor is above the item I get no effect, but I want the same effect like using NSOutlineView in AppKit. Here's an example from the Finder:

Drag and drop in the Finder sidebar

As you can see I have implemented highlightedItem in the code above. I can use it to check if an item is targeted and draw a background:

 .background {
       if item == highlightedItem {
            RoundedRectangle(cornerRadius: 5)
            .fill(Color.blue)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
       }
}

But that does not look quite the same:

own implementation of sidebar drop target

Interestingly the effect I want is the same you get if you use a selection for the sidebar list like: List(selection: $selectedItem)

There must be a native way to do this so I don't have to fake it and get something that does not look quite right.


Solution

  • shufflingb's workaround is nice but can have performance side effects and I've found a simpler solution.

    I've made a view for each list item and used .listRowBackground() for the background effect, not just .background:

    struct SidebarItemView: View {
        @ObservedObject var item: Item
        @State private var isTargeted = false
    
        var body: some View {
            NavigationLink(value: item) {
                Label {
                    Text(item.title)
    
                } icon: {
                    Image(systemName: "folder")
                }
                .onDrop(of: [.image], isTargeted: $isTargeted, perform: { itemProviders in
                ...
                return true
                })
            }
            .listRowBackground(
                Color.secondary
                    .cornerRadius(5)
                    .opacity(isTargeted ? 1.0 : 0.0)
                    .padding(.horizontal, 10)
            )
        }
    }