作为练习,我正在尝试将一个有效的 Swift AppKit 程序移植到 SwiftUI(适用于 macOS)。我的程序将从 Finder 拖到程序坞的文件,并在后端处理它们的 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;我能找到的每个示例都将itemProvider的处理限制在第一个项目。我无法在其上获取循环/迭代器进行编译。
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”)与UTTypes不同,UTTypes被记录为“统一类型标识符”。(Xcode仍然在.plist文档类型中使用旧式UTI。)
考虑我的代码中的这个片段:...info.itemProviders(用于:[。文本])...它在没有“导入UniformTypeIdentifiers”的情况下编译。但是将类型显式化-...info.itemProviders(用于:[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中的NSApplication ationAgent ate(例如,参见上面的“Onto Dock”部分)接收一个文件名数组,其中包括在单个拖动操作中拖到Dock上的每个url。
很明显,斯威夫特。onOpenURL()没有这个能力。还是个大问题。有什么想法吗?
多亏了这个工具,https://swiftui-lab.com/companion/ ,以及这个网站,https://swiftui-combine.com/posts/ultimate-guide-to-swiftui2-application-lifecycle/,我找到了我问题第一部分的部分解决方案 - 从拖动到应用程序的 Dock 图标打开文件。
从一个新的项目开始,项目-
import SwiftUI
@main
struct TestOnOpenURLApp: App {
var body: some Scene {
WindowGroup {
ContentView().onOpenURL{url in
print(url.lastPathComponent)
}
}
}
}
很简单。不幸的是,无论文件数量如何,它只响应1-3次(似乎是随机的)(
SwiftUI很优雅,对小工具很有用,但是参差不齐的实现和糟糕的留档(从一开始就应该是https://swiftui-lab.com/companion/)表明它还没有准备好独立进行全功能桌面编程。感谢所有回复的人!
不知道这样会不会有帮助,最近一直在解决这个问题。事实证明,从2023年1月11日起,有一种方法可以使用app delegate在doc图标或应用程序的finder图标上放置多个URL。在我的例子中,我只对搜索删除的目录感兴趣,所以我把URL保存在一个数组中
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
}
}