SwiftUI 拖放文件

Posted

技术标签:

【中文标题】SwiftUI 拖放文件【英文标题】:SwiftUI Drag and Drop files 【发布时间】:2020-03-24 12:41:46 【问题描述】:

我正在尝试向我的 SwiftUI Mac 应用程序添加“拖放”手势/功能。

我想将文件从我的系统/桌面拖放到我的应用程序中。我发现它在常规 Swift 中是可能的。我现在正在尝试在 SwiftUI 中执行此操作。

我在 SwiftUI for Views 中找到了一个 onDrop() 函数。但是,看起来这仅适用于我的应用程序中的内部手势。我想从外面拖拽文件。

在 Swift 中,您需要为拖动的类型注册 NSView。

registerForDraggedTypes([kUTTypeFileURL,kUTTypeImage])

我想创建一个 NSViewRepresentable 并将其包装到我的 SwiftUI 视图中。

这是我想出的代码,但是我不能调用registerForDraggedTyped

final class DragDropView: NSViewRepresentable 

    func makeNSView(context: NSViewRepresentableContext<DragDropView>) -> NSView 
        let view = NSView()

        view.registerForDraggedTypes([NSPasteboard.PasteboardType.pdf, NSPasteboard.PasteboardType.png])

        return view
    

    func updateNSView(_ nsView: NSView, context: NSViewRepresentableContext<DragDropView>) 

    

在 SwiftUI 中是否有更简单的解决方案?我很想使用那个 onDrop() 函数,但这不适用于外部文件,是吗?

【问题讨论】:

考虑alfianlosari.com/posts/… 【参考方案1】:

这是一个拖放演示,使用 Xcode 11.4 / macOS 10.15.4 进行了测试。

初始 image 位于资产库中,接受拖放(仅为简单起见)作为来自 Finder/Desktop(拖放)和 TextEdit(拖动)的文件 url,为 TIFF 表示注册拖放。

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)
    

【讨论】:

Asperi 你太棒了.. 我要怎么感谢你?! :) 来自德国的问候 @Asperi - 我正在尝试在 macOS 11 中使用它,并且 previewImageHander 永远不会被调用 - 知道为什么会这样吗?【参考方案2】:

正如 OP 所述,这适用于 Mac OS 应用程序。

如果你愿意

    从 Finder 打开 从 Finder 中拖动 并从其他地方(如网站或 iMessage)拖动

然后试试这个(不包括从这个视图拖动图像)。

XCode 11+ / SwiftUI 1.0+ / Swift 5:

从 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"])
                ))
            
        
    


SwiftUI 视图


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 
        
        


注意:不需要使用“public.image”标识符。

如果您需要将结果作为 PNG 数据的可选扩展(我已上传到 Firebase 存储):

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

【讨论】:

【参考方案3】:

你不能用onDrop(of:isTargeted:perform:)吗?您可以在 of 参数中传递您支持的类型数组。

【讨论】:

感谢您的回答 :) 我试过了,但我认为它不起作用。这应该适用于外部文件吗?我会再试一次,一秒钟 是这样使用的 .onDrop(of: ["png", "pdf", "jpg", "jpeg"], isTargeted: $target, perform: onFileDrop) 但是如果我拖动文件在它上面,什么都没有发生。我也没有得到那个鼠标加指示 正如 Asperi 上面所说,onDrop 实际上也像您所说的那样工作。我会检查我做错了什么。感谢您的帮助!! 它应该像.onDrop(of: [.png, .pdf, .jpeg],因为你需要在那里作为UTType。

以上是关于SwiftUI 拖放文件的主要内容,如果未能解决你的问题,请参考以下文章

UICollectionView 与 SwiftUI + 拖放重新排序可能吗?

SwiftUI:如何在 macOS 上从 Mail 中拖放电子邮件

如何在 SwiftUI (macOS) 中拖放未捆绑的图像

SwiftUI - iPad 上的拖放重新排序是不是可以在没有 EditMode 的情况下工作?

SwiftUI:如何从 macOS 上的联系人中拖放联系人

如何修改拖放预览SwiftUI的背景颜色和形状?