最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - Preventing Accidental Self-Drop in Drag and Drop Operations - Stack Overflow

programmeradmin0浏览0评论

I want to be able to drag and drop a png file into my app, but that's not my issue. My issue is when the user decides to drag image and drop it—say, onto the desktop—it works fine. However, if the user accidentally drops the dragged image back into the app, I want to ensure that my app does not process the dropped image again. I also tried to check if I could get a completion callback on drag, but it seems that onDrag executes before the user actually drops the item onto the desktop.

Also, I am not interested in reprocessing dropped items to check whether the dragged and dropped item is the same. That’s already too late because the damage is done, and it's an expensive operation. Instead, I want to create a flag that prevents onDrop from taking action while onDrag is actually happening.

I created an isDragging state to prevent the app from mistakenly processing an already created image again during a drop. However, it also prevents new images from being dropped. As another approach, I tried creating a custom NSItemProvider to run code in deinit, but I encountered the error:

'Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).'

import SwiftUI

struct ContentView: View {
    @State private var nsImage: NSImage = NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil) ?? NSImage()
    
    @State private var isDragging: Bool = false
    @State private var isTargeted: Bool = false

    var body: some View {
        VStack {
            Image(nsImage: nsImage) // Display the NSImage from SF Symbol
                .frame(width: 100, height: 100)
                .background(Color.white)
                .foregroundStyle(Color.black)
                .border(Color.gray, width: 2)
                .onDrop(of: [.image], isTargeted: nil, perform: handleDrop)
                .onDrag {
                    
                    // This prevents dropping the same image again by mistake and also prevents new image from being dropped back after onDrag.
                    // Preventing new image drop due to "isDragging = true" is an issue, because the app thinks the dragging is not finished.
                    // Also, I cannot use DispatchQueues here because I do not know how long the user will be interacting with dragging.
                    isDragging = true
                    
                    return handleDrag()
                }

            Button("Change Image to heart") {
                nsImage = NSImage(systemSymbolName: "heart.fill", accessibilityDescription: nil) ?? NSImage()
            }
            
            Button("Change Image to star") {
                nsImage = NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil) ?? NSImage()
            }
        }
        .onChange(of: isTargeted) { newValue in
            
            // Reset isDragging when the drag operation is no longer targeting the view.
            // However, if the user enters the target area more than once, isDragging is set back to false,
            // causing the app to think the drag is over and accept the same image as new image.
            // Also, I cannot use DispatchQueues here because I do not know how long the user will be interacting with dragging.
            // Even if I use a random value as a delay, let's say 0.5 seconds, if the user keeps dragging the same image multiple times in and out,
            // eventually the DispatchQueues would be fired, and the app would accept the same image as new image because isDragging is set back to false.

            if !newValue {
                // isDragging = false
            }
        }
    }

    private func handleDrop(providers: [NSItemProvider]) -> Bool {
        
        // Check if the .onDrag operation is still in progress and originated from within the app
        if (isDragging) {
            isDragging = false
            print("onDrop Canceled! same image.")
            return false
        }
        
        guard let provider = providers.first else {
            return false
        }

        if provider.canLoadObject(ofClass: NSImage.self) {
            _ = provider.loadObject(ofClass: NSImage.self) { (droppedImage, error) in
                if let newImage = droppedImage as? NSImage {
                        nsImage = newImage
                }
            }
            print("Successful onDrop.")
            return true
        }

        return false
    }

    private func handleDrag() -> NSItemProvider {
        // Convert the system image to PNG data
        if let tiffData = nsImage.tiffRepresentation,
           let bitmapImage = NSBitmapImageRep(data: tiffData),
           let pngData = bitmapImage.representation(using: .png, properties: [:]) {
            
            // Create the NSItemProvider with PNG data
            let itemProvider = NSItemProvider(item: pngData as NSSecureCoding, typeIdentifier: "public.png")
            return itemProvider
            
            // Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
            // let customNSItemProvider = CustomNSItemProvider(item: pngData as NSSecureCoding, typeIdentifier: "public.png", actionOnDeinit: {
            //    print("deinit")
            // })
            // return customNSItemProvider
            
        } else {
            print("Failed to convert image to PNG data")
        }

        return NSItemProvider()
    }
}





class CustomNSItemProvider: NSItemProvider {
    
    var actionOnDeinit: (() -> Void)?
    
    init(item: (any NSSecureCoding)?, typeIdentifier: String?, actionOnDeinit: (() -> Void)?) {
          super.init(item: item, typeIdentifier: typeIdentifier)
    }
    
    deinit {
        // trying to "isDragging = false" here
        actionOnDeinit?()
    }
}
发布评论

评论列表(0)

  1. 暂无评论