【问题标题】:macOS SwiftUI application - Get URL from Drag and drop Finder Files onto Dock or ViewmacOS SwiftUI 应用程序 - 从将 Finder 文件拖放到 Dock 或 View 中获取 URL
【发布时间】:2022-02-08 01:19:13
【问题描述】:

作为练习,我正在尝试将一个正常工作的 Swift AppKit 程序移植到 SwiftUI(对于 ma​​cOS)。我的程序将文件从 Finder 拖到 Dock 上,并在后端处理它们的 URL,这在很大程度上独立于 Apple API。所以我正在尝试接收从 Finder 拖到我的 Dock 图标或视图上的文件的 URL - 我不是在寻找文件内容

到码头:

我无法从使用 AppDelegateAdapter 拖到 Dock 的文件中捕获 URL,我认为原因很明显,但我想我可能会走运。该程序确实接受拖到 Dock 上的文件/文件,但只会打开另一个视图实例 - 每次拖拽一个,无论文件数量如何。

import SwiftUI

class AppDelegate: NSObject, NSApplicationDelegate {
    // application receives something on drag to Dock icon - it opens a new View
    // undoubtedly ignored because there's no NSApplication instance
    func application(_ sender: NSApplication, openFiles filenames: [String]) {
        for name in filenames{
            print("This is not called when file dropped on app's Dock icon: \(name)")
        }
    }

    func applicationDidFinishLaunching(_ notification: Notification) {
        print("This works")
    }
}

@main
struct TestSwift_DnDApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            DualPasteboardView().navigationTitle("Pasteboard Test")
        }
    }
}

进入视图:

我的视图有两个框,分别响应 URL 或文本的拖动。我不知道如何从 DropInfo/NSItemsProvider 中哄骗拖动的数据。

几个小时后,我编译了结构 TextDropDelegate,但它不起作用 - 总是出现 nil。由于我不知道字符串的简单情况的正确语义,所以我放弃了 URLDropDelegate。后者更难,因为 NSSecureCoding 对 URL/NSURL 类型的支持不明确。另外,有可能/很可能会删除多个 URL;我能找到的每个示例都将 itemProviders 的处理限制为第一项。我无法在其上编译循环/迭代器。

import SwiftUI
import UniformTypeIdentifiers  // not clear if required

/*
 starting point for view design:
 https://swiftontap.com/dropdelegate
 UTType description and pasteboard example source in comment below:
 https://developer.apple.com/videos/play/tech-talks/10696 -
 */

struct DualPasteboardView: View {
    var urlString = "Drag me, I'm a URL"
    var textString = "Drag me, I'm a String"
    var body: some View {
        VStack{
            VStack{
                Text("Labels can be dragged to a box")
                Text("Red Box accepts URLs dragged").foregroundColor(.red)
                Text("(simulated or from Finder)").foregroundColor(.red)
                Text("Green Box accepts Text").foregroundColor(.green)
            }.font(.title)
            
            HStack {
                Text(urlString)
                    .font(.title)
                    .foregroundColor(.red)
                    .onDrag { NSItemProvider(object: NSURL())}
                    //bogus url for testing d'n'd, ignore errors

                // Drop URLs here
                RoundedRectangle(cornerRadius: 0)
                    .frame(width: 150, height: 150)
                    .onDrop(of: [.url], delegate: URLDropDelegate())
                    .foregroundColor(.red)
                    
            }
            HStack {
                Text(textString)
                    .font(.title)
                    .foregroundColor(.green)
                    .onDrag { NSItemProvider(object: textString as NSString)}
                
                // Drop text here
                RoundedRectangle(cornerRadius: 0)
                    .frame(width: 150, height: 150)
                    .foregroundColor(.green)
                    .onDrop(of: [.text], delegate: TextDropDelegate())
                    /*.onDrop(of: [.text], isTargeted: nil ){ providers in
                            _ = providers.first?loadObject(of: String.self){
                            string, error in
                            text = string
                            }
                        return true
                        } // see comment below for approx. syntax from Apple tech talk */
                    }
        }.frame(width: 500, height: 500) //embiggen the window
    }
}

struct URLDropDelegate: DropDelegate {
    func performDrop(info: DropInfo) -> Bool {
        //no idea
        return true
    }
}

struct TextDropDelegate: DropDelegate {
    func performDrop(info: DropInfo) -> Bool {
        var tempString: String?
        _ = info.itemProviders(for:[.text]).first?.loadObject(ofClass: String.self) {
            string, error in
            tempString = string
        }
        print(tempString ?? "no temp string")
        // always prints 'no temp string'
        // should be "Drag me, I'm a String"
        return true
    }
    
    func validateDrop(info: DropInfo) -> Bool {
        //deliberately incorrect UTType still returns true
        return info.itemProviders(for: [.fileURL]).count > 0
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        DualPasteboardView()
    }
}

/* syntax used in  https://developer.apple.com/videos/play/tech-talks/10696/ @22:27
(this generates compiler errors, including in .onDrop(...), and doesn't make sense to me either)
 
 struct MyGreatView:View{
     @State var text: String? = nil
     
     var body: some View{
         MyGreatView(content: $text)
             .onDrop(of: [.text], isTargeted:nil){providers in
                 _ = providers.first?loadObject(of: String.self){
                     string, error in
                     text = string
                 }
                 return true
             }
     }
 }
*/ 

我不是一个有经验的 Swift 程序员,所以非常欢迎温柔的帮助。

(使用 XCode 13.2.1、Big Sur 11.6.2、SwiftUI 2.x?)

可选的咆哮:在 macOS 上 SwiftUI 的缓慢吸收不足为奇。即使跨平台使用可能不同,文档也很差,并且非常强调 iOS。我不清楚(直到我偶然发现源代码中提到的 Apple 视频)熟悉的 UTI(“public.jpeg”)与 UTType 不同,UTType 被记录为“统一类型标识符”。 (Xcode 仍然在 .plist 文档类型中使用旧式 UTI。)

考虑我的代码中的这个片段:...info.itemProviders(for:[.text])... 它编译时没有“import UniformTypeIdentifiers”。但是明确类型 - ...info.itemProviders(for:[UTType.text])... - 没有导入就无法编译。编译器认为没有导入的 [.text] 的类型/值是什么?

这是众多挫折之一 - 在网络上对面向桌面的功能的有限讨论、不编译/运行的准系统 Apple 示例文件(至少在我的设置中)等等 - 使得在 macOS 上使用 SwiftUI一件苦差事。


较早前的(自我)答案略有改进 - 将身体场景的内容替换为:

        WindowGroup {
            let vm = ViewModel()
            ContentView(viewModel: vm)
                .frame(minWidth: 800, minHeight: 600)
                .handlesExternalEvents(preferring: ["*"], allowing: ["*"])
                .onOpenURL{ url in
                    vm.appendToModel(url: url)
                }
        }

.handlesExternalEvents(preferring: ...) 禁止在每次放置时创建新的内容视图(= macOS 中的窗口)。使用“*”匹配每个外部事件。这可能不是一个好主意。使用更好的方法留给读者作为练习,但请分享。

.onOpenURL(...) 中,我绕过视图并将网址直接发送到 ViewModel - 此处未记录。这对我的应用程序很有用,但是...

每次拖动只传递一个 url,即使许多文件是拖动操作的一部分。 AppKit 中的 NSApplicationDelegate(例如,参见上面的“Onto Dock”部分)接收一个文件名数组,其中包括在单个拖动操作中拖动到 Dock 上的每个 url。

显然,SwiftUI .onOpenURL() 没有这种能力。还是个大问题。有什么想法吗?

【问题讨论】:

    标签: swift macos swiftui drag-and-drop


    【解决方案1】:

    感谢这个工具,https://swiftui-lab.com/companion/ 和这个网站,https://swiftui-combine.com/posts/ultimate-guide-to-swiftui2-application-lifecycle/,我找到了我问题第一部分的部分解决方案 - 从拖动到应用程序的 Dock 图标上打开文件。

    从一个新项目开始,Project->Info->Document Types->Identifier 设置为“public.item”(打开任何东西),然后

    import SwiftUI
    
    @main
    struct TestOnOpenURLApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView().onOpenURL{url in
                    print(url.lastPathComponent)
                }
            }
        }
    }
    

    足够简单。不幸的是,它只响应 1-3 次(看似随机),无论拖动的文件数量(>=3)如何。除非我遗漏了什么,否则这个 SwiftUI 的不足对我的项目来说是一个阻碍。另外,我真的很想在应用程序的模型中收到一条打开消息,而不是创建一堆新视图。理想情况下,拖动的项目将分组到一个数组中,如 NSApplicationDelegate,用于预处理。

    SwiftUI 很优雅,对小工具很有用,但参差不齐的实现和糟糕的文档(从一开始就应该像 https://swiftui-lab.com/companion/)表明它还没有准备好独立用于功能齐全的桌面编程。感谢所有回复的人!

    【讨论】:

      猜你喜欢
      • 2011-03-10
      • 1970-01-01
      • 2016-02-20
      • 1970-01-01
      • 1970-01-01
      • 2013-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多