🫠

UICollectionViewDropDelegateは壊れている

2025/02/15に公開

未来の自分と同じ問題に引っかかった誰かのための備忘録。
インターネットのどこにも書いていなかったので

UICollectionViewがDropを受け取る場合、次のUICollectionViewDropDelegateの次のメソッドを定義してdropを受け取ります。

func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)

多くの場合次のようにしてアイテムを受け取ると考えられます。

func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
    for dropItem in coordinator.items {
        let provider = dropItem.dragItem.itemProvider
        // 受け取り処理
        // provider.loadObject, provider.loadFileRepresantation など
    }    

}

しかしこの時、awaitを使うために次のようにTaskを使ったりDispatchQueue.main.asyncを利用してこのメソッドのスコープから出ると全てのload*系メソッドが壊れます。

アプリ内では壊れない、アプリ外からのDropで壊れる。

func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
    Task { // Contextを引き継ぐので@MainActor
        for dropItem in coordinator.items {
            let provider = dropItem.dragItem.itemProvider
            // 受け取り処理
            // provider.loadObject, provider.loadFileRepresantation などのメソッドが壊れ、completionHandlerは呼ばれず、awaitは無限に続く
        }    
    }

}

例えばloadFileRepresentationcompletionHandlerを即座に呼び出さずに解放しますし、loadItemは決して解決されないasync関数になってしまいます(いつまで待ってもawaitから先に行かない)

これの酷いのはloadItemはasync関数である(正確には自動Convertされたためcompletion handlerが非推奨になっている)のでTaskを作って呼び出す必要があるにも関わらず、Taskを作って呼び出すとメソッドから出ることになるので確定で壊れることです。

しかし、NSItemProvider.canLoadNSItemProvider.registeredContentTypesは特に変化しないためこのことに気が付きにくくなっています。

AppleのDocumentにもメソッドから出ては行けない記載もなかったため、iOSのバグか暗黙の仕様と思われます。

以上

Discussion