UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Drag Drop Table row using Transferable?

Forums > SwiftUI

I have not found an example where drag and drop can be used to "move" a Table row using the newer Transferable protocol. Anyone have some sample code? If not doable, is this coming to Table in the future?

Essentially, dnd is a gesture to reassign a property value which results in a refiguring of the Table. I have a Table of hierarchical SwiftData objects and want the user to be able to "drop" the row between children in a new family or on a row, thus triggering an update to the parent property.

2      

Based on the sparse examples I have found, I thought this would work but it does not compile:

Table(
   of: Account.self,
   selection: $selection,
   sortOrder: $sortOrder,
   columnCustomization: $columnCustomization
 )
 {
   accountNameTableColumn() // this is a TableColumn
 } 
rows: {
 TableRow(theNetResourceAccount)
   .draggable {
     theNetResourceAccount
   }
   .dropDestination(for: Account.self) { items, location in
     for item in items {
          print("item: ", item)
          print("location: ", location)
      }
   }
}

Still looking for working example code and insights. Thanks.

   

Making some progress, thanks in part to https://www.youtube.com/watch?v=lsXqJKm4l-U&t=1100s (Using Xcode 15.3, coding an exclusively MacOS app)

In my .app file:

extension UTType {
  static var account: UTType = UTType(exportedAs: "com.....account")
}

where com.....account is an Exported Type Identifier specified in the project Info tab

In my object file we add conformance with Transferable (it's already in conformance with Codable):

extension Account: Transferable {
  static var transferRepresentation: some TransferRepresentation {
    CodableRepresentation(contentType: .account)
  }
}

then in the view with the Table:

    TableRow(account)
        .draggable(account)
        .dropDestination(for: Account.self) { droppedAccounts in
//      for droppedAccount in droppedAccounts {
// do things like change parent
//      }
    }

The rows are draggable. A row highlights when targeted as a destination. The app hangs with no errors on drop. Drag and Drop to nowhere is tolerated (dragged item snaps back).

In the TableRowContent documentation example there is .itemProvider { account.itemProvider } TableRow modifier but Xcode won't compile with that line; maybe that's not applicable to SwiftData objects and perhaps there is some newer syntax I should use as that requires an NSItemProvider?

In the Sean Allen example noted above there is also a "return true", which also stops compilation when I add it.

Aside, I had to remove one of the return values from .dropDestination and I'm guessing that should be "destination"--a place on screen--and not the set of dropped items.

So half-way there. Why does the drop not exit gracefully (there's nothing to do yet, a print() statement is never reached before the hang). What am I missing?

   

Despite thinking I've made my whole SwiftData object Transferable and expecting that to be adequate I've resorted to passing a String instead of my object.

.draggable(object.id.description)
.dropDestination(for: String.self) { droppedIDstrings in
  for droppedIDstring in droppedIDstrings {
    print("droppedIDstring: ", droppedIDstring)
    print("UUID: ",  UUID(uuidString: droppedIDstring))
  }

Still only one returned argument, adding true to return or adding isTargeted:{} prevented compliation.

Since my objects all have unique names it's simpler to drop a set of names.

.draggable(object.name)
.dropDestination(for: String.self) { droppedNames in
  for droppedName in droppedNames {
    if let droppedObject = someObjects.first(where: {$0.name == droppedName}) {
... // use droppedObject and object as needed
  }
}
return
}

Since I use this dropDestination modifier several times I have tried but failed to abstract this code. While in a struct as described in https://www.hackingwithswift.com/books/ios-swiftui/custom-modifiers compiler wants the usual two return arguments and "return true". Probably needs to be a specific TableRow return view.

 struct DropableTableRowModifier: ViewModifier {
    let object: Object: 
    let someObjects: [Object] 

  func body(content: Content) -> some View {
    content
      .dropDestination(for: String.self) { object Names in
      ...

   

Unless you want to stick with Transferable as a learning experience, a simpler solution would be to iterate the rows within a ForEach, to which you apply the onMove modifier to support dragging rows.

In this tutorial, you can append .onMove() to any of the ForEach's: https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-multi-column-lists-using-table

   

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.