SwiftUI Drag and Drop files

前端 未结 3 407
深忆病人
深忆病人 2020-12-06 15:05

I am trying to add a \"Drag and Drop\" gesture / function to my SwiftUI Mac application.

I want to drop files from my System/ Desktop into my Application. It is pos

相关标签:
3条回答
  • 2020-12-06 15:41

    As OP noted, this is only for a Mac OS application.

    If you want to

    1. open from Finder
    2. drag from Finder
    3. AND drag from elsewhere (like a website or iMessage)

    then try this (drag image from this view not included).

    XCode 11+ / SwiftUI 1.0+ / Swift 5:

    Required Extension for opening from Finder:

    extension NSOpenPanel {
        
        static func openImage(completion: @escaping (_ result: Result<NSImage, Error>) -> ()) {
            let panel = NSOpenPanel()
            panel.allowsMultipleSelection = false
            panel.canChooseFiles = true
            panel.canChooseDirectories = false
            panel.allowedFileTypes = ["jpg", "jpeg", "png", "heic"]
            panel.canChooseFiles = true
            panel.begin { (result) in
                if result == .OK,
                    let url = panel.urls.first,
                    let image = NSImage(contentsOf: url) {
                    completion(.success(image))
                } else {
                    completion(.failure(
                        NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to get file location"])
                    ))
                }
            }
        }
    }
    
    

    The SwiftUI Views

    
    struct InputView: View {
        
        @Binding var image: NSImage?
        
        var body: some View {
            VStack(spacing: 16) {
                HStack {
                    Text("Input Image (PNG,JPG,JPEG,HEIC)")
                    Button(action: selectFile) {
                        Text("From Finder")
                    }
                }
                InputImageView(image: self.$image)
            }
        }
        
        private func selectFile() {
            NSOpenPanel.openImage { (result) in
                if case let .success(image) = result {
                    self.image = image
                }
            }
        }
    }
    
    struct InputImageView: View {
        
        @Binding var image: NSImage?
            
        var body: some View {
            ZStack {
                if self.image != nil {
                    Image(nsImage: self.image!)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                } else {
                    Text("Drag and drop image file")
                        .frame(width: 320)
                }
            }
            .frame(height: 320)
            .background(Color.black.opacity(0.5))
            .cornerRadius(8)
                
            .onDrop(of: ["public.url","public.file-url"], isTargeted: nil) { (items) -> Bool in
                if let item = items.first {
                    if let identifier = item.registeredTypeIdentifiers.first {
                        print("onDrop with identifier = \(identifier)")
                        if identifier == "public.url" || identifier == "public.file-url" {
                            item.loadItem(forTypeIdentifier: identifier, options: nil) { (urlData, error) in
                                DispatchQueue.main.async {
                                    if let urlData = urlData as? Data {
                                        let urll = NSURL(absoluteURLWithDataRepresentation: urlData, relativeTo: nil) as URL
                                        if let img = NSImage(contentsOf: urll) {
                                            self.image = img
                                            print("got it")
                                        }
                                    }
                                }
                            }
                        }
                    }
                    return true
                } else { print("item not here") return false }
            }
        }    
    }
    
    

    Note: Have not needed to use the "public.image" identifier.

    Optional Extensions if you need the result as PNG data (I did to upload to Firebase Storage):

    extension NSBitmapImageRep {
        var png: Data? { representation(using: .png, properties: [.compressionFactor:0.05]) }
    }
    
    extension Data {
        var bitmap: NSBitmapImageRep? { NSBitmapImageRep(data: self) }
    }
    
    extension NSImage {
        var png: Data? { tiffRepresentation?.bitmap?.png }
    }
    
    // usage
    
    let image = NSImage(...)
    if let data = image.png {
         // do something further with the data
    }
    
    0 讨论(0)
  • 2020-12-06 15:43

    Can’t you use onDrop(of:isTargeted:perform:)? You can pass your array of supported types in the of argument.

    0 讨论(0)
  • 2020-12-06 16:02

    Here is a demo of drag & drop, tested with Xcode 11.4 / macOS 10.15.4.

    Initial image is located on assets library, accepts drop (for simplicity only) as file url from Finder/Desktop (drop) and to TextEdit (drag), registers drag for TIFF representation.

    struct TestImageDragDrop: View {
        @State var image = NSImage(named: "image")
        @State private var dragOver = false
    
        var body: some View {
            Image(nsImage: image ?? NSImage())
                .onDrop(of: ["public.file-url"], isTargeted: $dragOver) { providers -> Bool in
                    providers.first?.loadDataRepresentation(forTypeIdentifier: "public.file-url", completionHandler: { (data, error) in
                        if let data = data, let path = NSString(data: data, encoding: 4), let url = URL(string: path as String) {
                            let image = NSImage(contentsOf: url)
                            DispatchQueue.main.async {
                                self.image = image
                            }
                        }
                    })
                    return true
                }
                .onDrag {
                    let data = self.image?.tiffRepresentation
                    let provider = NSItemProvider(item: data as NSSecureCoding?, typeIdentifier: kUTTypeTIFF as String)
                    provider.previewImageHandler = { (handler, _, _) -> Void in
                        handler?(data as NSSecureCoding?, nil)
                    }
                    return provider
                }
                .border(dragOver ? Color.red : Color.clear)
        }
    }
    
    0 讨论(0)
提交回复
热议问题