macOS SwiftUI应用程序-通过将Finder文件拖放到Dock或View来获取URL

628mspwn  于 2023-01-12  发布在  Swift
关注(0)|答案(2)|浏览(178)

作为一个练习,我正在尝试将一个正在运行的Swift AppKit程序移植到SwiftUI(适用于macOS)。我的程序接收从Finder拖到Dock上的文件,并在后端处理它们的URL,这在很大程度上独立于苹果的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。因为我不知道简单的String的正确语义,所以我放弃了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
             }
     }
 }
*/

我不是一个经验丰富的斯威夫特程序员,所以温和的帮助是最受欢迎的。
(使用XCode 13.2.1、大苏尔11.6.2和SwiftUI 2.x?)

  • 可选租金 *:MacOS对SwiftUI的缓慢使用并不令人感到意外。文档很差,而且过于强调iOS,即使跨平台使用情况可能有所不同。我当时并不清楚(直到我偶然发现源代码中提到的苹果视频)熟悉的UTI(“public.jpeg”)与UTType不同,它们被记录为“统一类型标识符”。(Xcode仍然在.plist文档类型中使用旧样式的UTI。)

考虑我的代码中的这个片段:... info.itemProviders(for:[.text])...它在没有“import UniformTypeIdentifiers”的情况下编译。但是显式类型-... info.itemProviders(for:[UTType.text])... -在没有导入的情况下不会编译。如果没有导入,编译器认为[.text]的类型/值是什么?
这是许多令人沮丧的事情之一--在网络上对面向桌面的功能的讨论有限,苹果的基本示例文件不能编译/运行(至少在我的设置中),等等--这使得在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(...)中,我绕过视图,直接将url发送到ViewModel -这里没有记录。

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

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

zte4gxcn

zte4gxcn1#

多亏了这个工具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/一样)表明它还没有准备好独立于全功能的桌面编程。

b09cbbtk

b09cbbtk2#

I don't know if this will be helpful, but I have been working

这个问题最近出现了。事实证明,从2023年1月11日起,有一种方法可以使用应用程序委托在文档图标或应用程序的查找器图标上放置多个URL。在我的情况下,我只对搜索放置的目录感兴趣,所以我将URL保存在一个数组中,并只处理与文件夹或卷对应的元素。此外,所有我需要的是一个单一的窗口应用程序。我已经测试了这与WindowGroup取代窗口()&它失败了,GRRRR!也许在SwiftUI中拖到一个应用程序或单窗口应用程序的Dock图标是如此明显,以至于没有人愿意分享它,但我花了一段时间,&许多徒劳的搜索,来解决这个问题。所以,为了分享,我希望这能帮助一些人:

struct MyApp: App {
    @NSApplicationDelegateAdaptor private var appDelegate: AppDelegate
    var body: some Scene {
        Window("MyApp", id: "main") {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, NSApplicationDelegate {
    func application(_ sender: NSApplication, open urls: [URL]) {
    // Use open, not openFiles!
        print("appDelegate dropped urls:", urls) // diagnostic
    }
}

相关问题