У меня есть кнопки импорта (и экспорта), которые отлично работают в представлении.
var body: some View {
Button(action: {
isImporting = true
}, label: {
Label("Import", systemImage: "square.and.arrow.down")
})
.fileImporter(
isPresented: $isImporting,
allowedContentTypes: [UTType.log, UTType.text, UTType.xml, UTType.zip],
allowsMultipleSelection: true
) { result in
switch result {
case let .success(files):
files.forEach { file in
guard file.startAccessingSecurityScopedResource() else { return }
.../...
file.stopAccessingSecurityScopedResource()
}
case let .failure(error):
print(error)
}
}
}
Но я также хочу, чтобы команды импорта/экспорта были доступны через меню «Файл» в приложении macOS.
struct ImportExportCommands: Commands {
@MainActor
var body: some Commands {
CommandGroup(replacing: .importExport) {
Section {
ImportButton()
.modelContainer(mdContainer.sharedModelContainer)
ExportButton()
.modelContainer(mdContainer.sharedModelContainer)
}
}
}
}
Код действия выполняется, но диалоговое окно файла не появляется.
Я ничего не нашел по этому поводу, поэтому полагаю, что это очень глупая вещь.
Как я могу открыть диалоговое окно fileImporter/fileExporter на экране из команды меню?
Проблема, на мой взгляд, заключается в том, что вам сначала нужно открыть окно, чтобы модификатор .fileImporter
заработал, но один из способов обойти это — использовать вместо него NSOpenPanel
.
Что-то вроде этого:
struct ImportExportCommands: Commands {
@Environment(\.openWindow) private var openWindows
@MainActor
var body: some Commands {
CommandGroup(replacing: .importExport) {
Section {
Button("Import") {
selectImportFiles()
}
}
}
}
private func selectImportFiles() {
let openPanel = NSOpenPanel()
openPanel.title = "Import files"
openPanel.prompt = "Import"
openPanel.canChooseFiles = true
openPanel.allowsMultipleSelection = true
openPanel.allowedContentTypes = [UTType.log, UTType.text, UTType.xml, UTType.zip]
openPanel.begin { result in
if result == .OK {
for fileUrl in openPanel.urls {
print(fileUrl.absoluteString)
}
}
}
}
}
У меня была аналогичная проблема в приложении SwiftUI. Что мне помогло, так это добавить целенаправленную привязку к представлениям меню.
Для добавления фокусированной привязки необходимо выполнить следующие действия:
FocusedValues
SwiftUI для созданного вами ключа целевого значения.Структура ключа с фокусированным значением должна соответствовать протоколу FocusedValueKey
. Укажите псевдоним типа для выбранного типа значения. Следующий код создает сфокусированный тип значения для структуры документа SwiftUI:
struct DocumentFocusedValueKey: FocusedValueKey {
typealias Value = Binding<Document>
}
Вычисляемое свойство расширения FocusedValues
требует, чтобы вы добавили методы получения и установки для структуры ключа с фокусным значением, которую вы создаете.
extension FocusedValues {
var document: DocumentFocusedValueKey.Value? {
get {
return self[DocumentFocusedValueKey.self]
}
set {
self[DocumentFocusedValueKey.self] = newValue
}
}
}
Используйте оболочку свойства @FocusedBinding
, чтобы добавить целенаправленную привязку к представлениям меню.
@FocusedBinding(\.document) var theDocument: Document?
Значение, которое вы указываете после @FocusedBinding
, — это имя вычисляемого свойства, которое вы добавили в качестве расширения к FocusedValues
.
Используйте модификатор .focusedSceneValue
, чтобы установить значение сцены в фокусе. В следующем примере значение сцены в фокусе задается для документа в приложении на основе документов:
.focusedSceneValue(\.document, file.$document)
В этом примере вы должны установить модификатор .focusedSceneValue
в структуре приложения.
У меня была эта проблема какое-то время, и я ничего не нашел по этому поводу. Но наконец я нашел эту страницу:
«.fileExporter и .fileImporter не будут работать внутри меню в SwiftUI» https://fpposchmann.de/fileexporter-and-fileimporter-wont-work-inside-a-menu-in-swiftui/ Спасибо Фрэнку-Питеру за идею.
Я переработал свой код, и теперь команды в CommandGroup работают как надо.
private struct FactorizedIExportButton: View {
@Binding var isExporting: Bool
var body: some View {
Button(action: {
isExporting = true
}, label: {
Label("Export…", systemImage: "square.and.arrow.up")
})
.keyboardShortcut("E")
}
}
struct ImportButton: View {
@State var isImporting: Bool = false
var body: some View {
FactorizedImportButton(isImporting: $isImporting)
.fileImporter(
isPresented: $isImporting,
allowedContentTypes: [.text, .kmz],
allowsMultipleSelection: true
) { result in importFiles(result) }
}
}
struct ExportButton: View {
@State var isExporting: Bool = false
var document = TextDocument()
var body: some View {
FactorizedIExportButton(isExporting: $isExporting)
.fileExporter(
isPresented: $isExporting,
document: document,
contentTypes: [.gpx],
defaultFilename: "Export"
) { result in exportFile(result) }
}
}
struct ImportExportCommands: Commands {
@State var isImporting: Bool = false
@State var isExporting: Bool = false
var document = TextDocument()
@MainActor
var body: some Commands {
CommandGroup(replacing: .importExport) {
Section {
FactorizedImportButton(isImporting: $isImporting)
FactorizedIExportButton(isExporting: $isExporting)
}
.fileImporter(
isPresented: $isImporting,
allowedContentTypes: [.text, .kmz],
allowsMultipleSelection: true
) { result in importFiles(result) }
.fileExporter(
isPresented: $isExporting,
document: document,
contentTypes: [.gpx],
defaultFilename: "Export"
) { result in exportFile(result) }
}
}
}