Can I disable a SwiftUI View from being a draggable source, but still leave it enabled as a dropDestination?

My app has a collection of Cell Views. some of the views' model objects include a Player, and others do not. I want to use drag and drop to move a player from one cell to another. I only want cells that contain a player to be valid drag sources. All cells will be valid drop destinations. When I uncomment the .disabled line both drag and drop become disabled. Is it possible to keep a view enabled as a dropDestination but disabled as a draggable source?

        VStack {
            Image("playerJersey_red")
            if let player = content.player {
                Text(player.name)
            }
        }
        .draggable(content)
//        .disabled(content.player == nil)
        .dropDestination(for: CellContent.self) { items, location in

One thing I tried was to wrap everything in a ZStack and put a rectangle with .opacity(0.02) above the Image/Text VStack. I then left draggable modifying my VStack and moved dropDestination to the clear rectangle. This didn't work as I wasn't able to initiate a drag when tapping on the rectangle. Any other ideas or suggestions? thanks

Thank you for your question. I recommend providing a focused sample project, such as the image Image(“playerJersey_red”), which we will all be missing. I will need to replace it with a different image. Regarding the dropDestination cellContent, I understand that you define it, but it would be beneficial to have a sample that can be executed quickly and shared with others for verification.

I think it is possible to have a view enabled as a drop destination but disabled as a draggable source based on certain conditions. The issue you're encountering with the .disabled modifier affecting both dragging and dropping could be because it disables all user interactions for the view. Instead, you should conditionally apply the .draggable modifier only when there is a player available?

Use content.player.map { $0 } to provide a value only when player is not nil. This makes the view draggable only if a player exists and should keep the .dropDestination modifier without any conditions since you want all cells to be valid drop targets? I need to play with it and see I think as I haven’t even done that for so long, let you get the focused sample posted so I can start working on those 2 modifications to see if I’m correct this time. Invite swiftUI experts here to provide their opinion, of course as probably there are better ways to do that.

Albert Pascual
  Worldwide Developer Relations.

Hi Albert, Thank you for your interest in my question :-) here is a barebones sample project to demonstrate my challenge.

There are two player cells, and I want to be able to move the player (Messi) from the cell on the left to the cell on the right using a drag gesture. However I do not want the cell on the right to be a drag source (because it is empty). The only way I can find to prevent the right side cell from being a drag source also prevents it from being a drop destination. Is there a way I can configure PlayerView to:

  1. be a drag source when it contains a player
  2. always be a drop destination.

thanks again for your interest and assistance with this.

struct ContentView: View {
    var body: some View {
        HStack {
            PlayerView(content: .init(player: .mock))
            PlayerView(content: .init())
        }
        .padding()
    }
}

struct PlayerView: View {
    var content: CellContent
    var body: some View {
        VStack {
            Image("jersey")
            if let player = content.player {
                Text(player.last)
            }
        }
        .draggable("player")
//        .disabled(content.player == nil)
        .dropDestination(for: String.self) { items, location in
            if let firstItem = items.first {
                print("\(firstItem)")
            }
            return true
        }
    }
}

class CellContent: Identifiable, Codable {
    let id: UUID
    var player: Player?
    
    init(player: Player? = nil) {
        self.id = UUID()
        self.player = player
    }
}
class Player: Codable, Identifiable {
    static let mock = Player(first: "Leo", last: "Messi", number: "10")
    var first: String
    var last: String
    var number: String
    let id: UUID
    
    init(first: String, last: String, number: String, absent: Bool = false) {
        self.first = first
        self.last = last
        self.number = number
        self.id = UUID()
    }
}

hi @DTS Engineer / Albert, Just in case you have the time to investigate this, I am still very interested in finding a fix/workaround to the problem I describe in this question. thanks in advance for any guidance,

Sorry for the delay, I didn't get the notification.

Thanks for the code, I would recommend to add a link to a focused project where I can download and play with it as of course without the images nor drop targets is hard to see what you are trying to accomplish and will help many developers here figure it out.

Customize the drag preview to make the interaction more intuitive. You can modify the draggable modifier

.draggable("player", preview: {
    VStack {
        Image("jersey")
           .resizable()
           .frame(width: 50, height: 50)
        if let player = content.player {
            Text(player.last)
               .font(.caption)
        }
    }
})

Currently, the drop handler just prints the item. You might want to update the CellContent's player based on the drop?

.dropDestination(for: String.self) { items, location in
    if let firstItem = items.first {
        print("Dropped: \(firstItem)")
        // Update player logic here if needed
        // e.g., content.player = updatedPlayerBasedOn(firstItem)
    }
    return true
}

Looking forward to your focused sample.

That'll help us better understand what's going on. If you're not familiar with preparing a test project, take a look at Creating a test project.

Looking forward to SwiftUI experts to jump and also help on this one!!

Albert Pascual
  Worldwide Developer Relations.

Can I disable a SwiftUI View from being a draggable source, but still leave it enabled as a dropDestination?
 
 
Q