Hi team,
I'm running into the following issue, for which I don't seem to find a good solution.
I would like to be able to drag and drop items from a view into empty space to open a new window that displays detailed information about this item.
Now, I know something similar has been flagged already in this post (FB13545880: Support drag and drop to create a new window on visionOS)
HOWEVER, all this does, is launch the App again with the SAME WindowGroup and display ContentView in a different state (show a selected product e.g.).
What I would like to do, is instead launch ONLY the new WindowGroup, without a new instance of ContentView.
This is the closest I got so far. It opens the desired window, but in addition it also displays the ContentView WindowGroup
WindowGroup {
ContentView()
.onContinueUserActivity(Activity.openWindow, perform: handleOpenDetail)
}
WindowGroup(id: "Detail View", for: Reminder.ID.self) { $reminderId in
ReminderDetailView(reminderId: reminderId! )
}
.onDrag({
let userActivity = NSUserActivity(activityType: Activity.openWindow)
let localizedString = NSLocalizedString("DroppedReminterTitle", comment: "Activity title with reminder name")
userActivity.title = String(format: localizedString, reminder.title)
userActivity.targetContentIdentifier = "\(reminder.id)"
try? userActivity.setTypedPayload(reminder.id)
// When setting the identifier
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(reminder.persistentModelID),
let jsonString = String(data: jsonData, encoding: .utf8) {
userActivity.userInfo = ["id": jsonString]
}
return NSItemProvider(object: userActivity)
})
func handleOpenDetail(_ userActivity: NSUserActivity) {
guard let idString = userActivity.userInfo?["id"] as? String else {
print("Invalid or missing identifier in user activity")
return
}
if let jsonData = idString.data(using: .utf8) {
do {
let decoder = JSONDecoder()
let persistentID = try decoder.decode(PersistentIdentifier.self, from: jsonData)
openWindow(id: "Detail View", value: persistentID)
} catch {
print("Failed to decode PersistentIdentifier: \(error)")
}
} else {
print("Failed to convert string to data")
}
}
Hi @simonroetzer ,
This is very doable! I'll add some code below.
First, create your main ContentView WindowGroup
as well as your DetailView WindowGroup
, and mark the DetailView one with handlesExternalEvents
@main
struct DragAndDropNewWindowApp: App {
@State private var text: String = "Drag me!"
var body: some Scene {
WindowGroup {
ContentView(text: $text)
}
WindowGroup(id: "detailView") {
DetailView(text: $text)
}
.handlesExternalEvents(matching: ["detailView"])
}
}
As you were doing, create a userActivity
and return an NSItemProvider
with that object in your ContentView
var body: some View {
Rectangle()
.fill(.green)
.frame(width: 200, height: 200)
.overlay {
Text(text)
}
.onDrag {
let userActivity = NSUserActivity(activityType: "drag")
userActivity.title = text
userActivity.targetContentIdentifier = "detailView"
userActivity.userInfo = ["first": text as String]
return NSItemProvider(object: userActivity)
}
}
Then, in your DetailView you can use .onContinueUserActivity
to handle any other things you want do to, like this:
.onContinueUserActivity("drag") { activity in
let _ = print(activity.title ?? "")
}
One thing, test this on-device if you can. I've noticed that sometimes the simulator doesn't show the dragging (not always, but sometimes). Letting you know this in case you come across this behavior; this isn't your code, it's the simulator.