【发布时间】:2022-02-08 01:19:13
【问题描述】:
作为练习,我正在尝试将一个正常工作的 Swift AppKit 程序移植到 SwiftUI(对于 macOS)。我的程序将文件从 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