I am new to SwiftUI, and I wrote a ReorderableForEach struct with onDrag and onDrop, which is working well on iOS17. However it's not wokring on iOS18 beta1 or beta2 on my iPhone or simulator. When I long press the item, nothing happens.
When I remove onDrop, I can perform a Drag animation, so I think something wrong with my onDrop code.
the whole code:
import UIKit
import SwiftUI
public typealias Reorderable = Identifiable & Equatable
struct GridData: Identifiable, Equatable {
let id: Int
}
public struct ReorderableForEach<Item: Reorderable, Content: View, Preview: View>: View {
@Binding
private var active: Item?
@State
private var hasChangedLocation = false
private let items: [Item]
private let content: (Item) -> Content
private let preview: ((Item) -> Preview)?
private let moveAction: (IndexSet, Int) -> Void
private var onDropAction: ((Item) -> Void)?
private var allowReorder: Bool
public init(
_ items: [Item],
active: Binding<Item?>,
allowReorder: Bool,
@ViewBuilder content: @escaping (Item) -> Content,
@ViewBuilder preview: @escaping (Item) -> Preview,
moveAction: @escaping (IndexSet, Int) -> Void,
onDropAction: ((Item) -> Void)? = nil
) {
self.items = items
self._active = active
self.allowReorder = allowReorder
self.content = content
self.preview = preview
self.moveAction = moveAction
self.onDropAction = onDropAction
}
public init(
_ items: [Item],
active: Binding<Item?>,
allowReorder: Bool,
@ViewBuilder content: @escaping (Item) -> Content,
moveAction: @escaping (IndexSet, Int) -> Void,
onDropAction: ((Item) -> Void)? = nil
) where Preview == EmptyView {
self.items = items
self._active = active
self.allowReorder = allowReorder
self.content = content
self.preview = nil
self.moveAction = moveAction
self.onDropAction = onDropAction
}
public var body: some View {
ForEach(items) { item in
if !allowReorder {
contentView(for: item)
}
else if let preview {
contentView(for: item)
.onDrag {
return dragData(for: item)
} preview: {
preview(item)
}
} else {
contentView(for: item)
.onDrag {
return dragData(for: item)
}
}
}
}
private func contentView(for item: Item) -> some View {
content(item)
.opacity(active == item && hasChangedLocation ? 0.5 : 1)
.onDrop(
of: [.text],
delegate: ReorderableDragRelocateDelegate(
item: item,
items: items,
active: $active,
hasChangedLocation: $hasChangedLocation
) { from, to in
withAnimation {
moveAction(from, to)
}
} onDropAction: { item in
onDropAction?(item)
}
)
}
private func dragData(for item: Item) -> NSItemProvider {
active = item
return NSItemProvider(object: "\(item.id)" as NSString)
}
}
struct ReorderableDragRelocateDelegate<Item: Reorderable>: DropDelegate {
let item: Item
var items: [Item]
@Binding var active: Item?
@Binding var hasChangedLocation: Bool
var moveAction: (IndexSet, Int) -> Void
var onDropAction: ((Item) -> Void)?
func dropEntered(info: DropInfo) {
guard item != active, let current = active else { return }
guard let from = items.firstIndex(of: current) else { return }
guard let to = items.firstIndex(of: item) else { return }
hasChangedLocation = true
if items[to] != current {
moveAction(IndexSet(integer: from), to > from ? to + 1 : to)
}
}
func dropUpdated(info: DropInfo) -> DropProposal? {
DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
hasChangedLocation = false
active = nil
onDropAction?(item)
return true
}
}
struct ReorderableDropOutsideDelegate<Item: Reorderable>: DropDelegate {
@Binding
var active: Item?
func dropUpdated(info: DropInfo) -> DropProposal? {
DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
active = nil
return true
}
}
public extension View {
func reorderableForEachContainer<Item: Reorderable>(
active: Binding<Item?>
) -> some View {
onDrop(of: [.text], delegate: ReorderableDropOutsideDelegate(active: active))
}
}
#Preview {
ReorderableForEach(
(1...10).map { GridData(id: $0) },
active: .constant(nil),
allowReorder: true
) { item in
Text("Item \(item.id)")
.padding()
.background(Color.blue)
.cornerRadius(8)
} preview: { item in
Text("Preview \(item.id)")
.padding()
.background(Color.red)
.cornerRadius(8)
} moveAction: { from, to in
print("Move from \(from) to \(to)")
}
}
Am I missing something or this is a bug on iOS18?
Thanks in advance.