You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于值类型数据对象的UITableView拖拽API集成方案咨询

Solutions for Adapting Swift Value Types to UITableView Drag-and-Drop APIs

Great question—this is a super common pain point when working with Swift value types and Apple's Objective-C-powered drag-and-drop system. The core issue is that NSItemProviderReading and NSItemProviderWriting are @objc class protocols, which Swift structs can't conform to directly. But you don't have to rewrite your structs as classes or hack your data model. Here are two clean, non-intrusive approaches that work with your existing value types.


This approach skips protocol conformance entirely by serializing your value type to Data, then using custom read/write logic with NSItemProvider. It's lightweight and requires zero changes to your struct definitions.

Step 1: Ensure your struct conforms to Codable

First, make sure your value type supports serialization (most structs can adopt Codable with no extra work):

struct DeliveryItem: Codable {
    let id: String
    let title: String
    // Add your other properties here
}

Step 2: Create drag items in your UITableViewDragDelegate

In your drag delegate method, serialize the struct to Data and wrap it in an NSItemProvider:

extension DeliveryViewController: UITableViewDragDelegate {
    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        guard let item = dataSource.itemIdentifier(for: indexPath) as? DeliveryItem else {
            return []
        }
        
        // Define a custom UTI to identify your data type
        let customUTI = UTType(exportedAs: "com.yourcompany.DeliveryItem", conformingTo: .data).identifier
        
        do {
            let itemData = try JSONEncoder().encode(item)
            let itemProvider = NSItemProvider(item: itemData, typeIdentifier: customUTI)
            
            // Optional: Register for generic data support for broader compatibility
            itemProvider.registerObject(itemData, visibility: .all)
            
            return [UIDragItem(itemProvider: itemProvider)]
        } catch {
            print("Failed to encode delivery item: \(error)")
            return []
        }
    }
}

Step 3: Handle drops in your UITableViewDropDelegate

Decode the Data back to your struct when handling the drop:

extension DeliveryViewController: UITableViewDropDelegate {
    func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool {
        let customUTI = UTType(exportedAs: "com.yourcompany.DeliveryItem", conformingTo: .data).identifier
        return session.hasItemsConforming(toTypeIdentifiers: [customUTI])
    }
    
    func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
        let destinationIndexPath = coordinator.destinationIndexPath ?? IndexPath(row: 0, section: 0)
        
        for dropItem in coordinator.items {
            let provider = dropItem.dragItem.itemProvider
            let customUTI = UTType(exportedAs: "com.yourcompany.DeliveryItem", conformingTo: .data).identifier
            
            provider.loadObject(ofClass: Data.self, forTypeIdentifier: customUTI) { [weak self] data, error in
                guard let self = self, let itemData = data as? Data, error == nil else {
                    return
                }
                
                do {
                    let droppedItem = try JSONDecoder().decode(DeliveryItem.self, from: itemData)
                    // Update your diffable data source on the main thread
                    DispatchQueue.main.async {
                        var snapshot = self.dataSource.snapshot()
                        snapshot.insertItems([droppedItem], beforeItem: snapshot.itemIdentifiers[destinationIndexPath.row])
                        self.dataSource.apply(snapshot, animatingDifferences: true)
                    }
                } catch {
                    print("Failed to decode delivery item: \(error)")
                }
            }
        }
    }
}

Option 2: Lightweight Temporary Wrapper Class

If your struct can't use Codable (e.g., it has non-serializable properties), create a throwaway wrapper class that conforms to the required protocols. This wrapper is only used during drag-and-drop and doesn't affect your core data model.

Step 1: Create the wrapper class

// This class is only used for drag-and-drop operations
fileprivate class DragValueWrapper<T>: NSObject, NSItemProviderReading, NSItemProviderWriting where T: Codable {
    let wrappedValue: T
    
    init(value: T) {
        self.wrappedValue = value
        super.init()
    }
    
    // MARK: NSItemProviderWriting
    static var writableTypeIdentifiersForItemProvider: [String] {
        return [UTType(exportedAs: "com.yourcompany.DragWrapper.\(String(describing: T.self))", conformingTo: .data).identifier]
    }
    
    func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
        do {
            let data = try JSONEncoder().encode(wrappedValue)
            completionHandler(data, nil)
        } catch {
            completionHandler(nil, error)
        }
        return nil
    }
    
    // MARK: NSItemProviderReading
    static var readableTypeIdentifiersForItemProvider: [String] {
        return writableTypeIdentifiersForItemProvider
    }
    
    static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> Self {
        let value = try JSONDecoder().decode(T.self, from: data)
        return self.init(value: value)
    }
}

Step 2: Use the wrapper in your drag delegate

extension DeliveryViewController: UITableViewDragDelegate {
    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        guard let item = dataSource.itemIdentifier(for: indexPath) as? DeliveryItem else {
            return []
        }
        
        let wrapper = DragValueWrapper(value: item)
        let itemProvider = NSItemProvider(object: wrapper)
        return [UIDragItem(itemProvider: itemProvider)]
    }
}

Step 3: Unwrap the value in your drop delegate

extension DeliveryViewController: UITableViewDropDelegate {
    func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
        let destinationIndexPath = coordinator.destinationIndexPath ?? IndexPath(row: 0, section: 0)
        
        for dropItem in coordinator.items {
            dropItem.dragItem.itemProvider.loadObject(ofClass: DragValueWrapper<DeliveryItem>.self) { [weak self] wrapper, error in
                guard let self = self, let valueWrapper = wrapper as? DragValueWrapper<DeliveryItem>, error == nil else {
                    return
                }
                
                DispatchQueue.main.async {
                    var snapshot = self.dataSource.snapshot()
                    snapshot.insertItems([valueWrapper.wrappedValue], beforeItem: snapshot.itemIdentifiers[destinationIndexPath.row])
                    self.dataSource.apply(snapshot, animatingDifferences: true)
                }
            }
        }
    }
}

Key Advantages of Both Solutions

  • No changes to your existing value types (avoids bugs and regression risks from struct-to-class conversions)
  • No modifications to your core data model or diffable data source setup
  • Both approaches work seamlessly with UITableView's built-in drag-and-drop delegates
  • Option 1 is the most lightweight, with zero extra class overhead

内容的提问来源于stack exchange,提问作者flanker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 00:24:05