SwiftUI + DocumentGroup в iOS / iPadOS: как переименовать текущий открытый документ

Мой вопрос касается DocumentGroup SwiftUI: используя простой проект на основе шаблона в Xcode (с использованием нового многоплатформенного шаблона приложения на основе документов), я могу создавать новые документы, редактировать их и т. Д. Кроме того, "вне" приложения , Я могу управлять файлом документа как таковым - перемещать его, копировать, переименовывать и т. Д.

По умолчанию все новые документы инициализируются именем «Без названия»; в основной точке входа приложения я могу получить доступ к URL-адресу файла:

      var body: some Scene {
    DocumentGroup(newDocument: ShowPLAYrDocument()) { file in
        // For example, this gives back the actual doc file URL:
        let theURL = file.fileURL
        ContentView(document: file.$document)
    }
}

Первый вопрос: как я могу отредактировать / изменить фактическое имя файла, когда документ «открыт», то есть когда код выполняется в области видимости ContentView? Отсутствие документации для SwiftUI очень затрудняет поиск ответов на подобные проблемы - я думаю, что обрушился на весь Интернет, но похоже, что ни у кого нет таких проблем, и если они есть, на их опубликованные вопросы нет ответов. - Я сам задал пару вопросов по другим вопросам, но даже комментариев не получил, не говоря уже об ответах.

У меня есть еще один вопрос, который, как мне кажется, в какой-то степени связан: я видел, например, в приложении «Файлы», что некоторые типы файлов, если они выбраны, могут отображать дополнительную расширенную информацию на панели «ИНФОРМАЦИЯ» для этого файла (например: видео файлы показывают размеры в пикселях, продолжительность и информацию о кодеке); документы моего приложения содержат несколько значений (в сохраненных данных), которые я хотел бы, чтобы пользователь мог "заглянуть" в средство выбора документов, не открывая файл как таковой, аналогично тому, как описано для приложения "Файлы"..

Мой второй вопрос: возможно ли это сделать, и если да, то где мне хотя бы начать искать ответы?Я предполагаю, что это «невозможно» с SwiftUI как таковым прямо сейчас, поэтому это должна быть интеграция с «обычным» Swift?

Заранее благодарим за любые рекомендации.

4 ответа

Решение

Проблемы, возникшие с указанным выше "решением", были связаны с (подтвержденной) ошибкой в .fileImporter модификатор - итак, это "работает", как бы хакерское оно есть.

Хорошо, вот в чем дело: мне «вроде как» удалось добиться того, чего я хотел, хотя это не выглядит (для меня) как самый «правильный» способ сделать это, И все еще есть проблема с процессом - хотя на данный момент я виню в этом (очевидно, известную) реализацию с ошибками, которая также вызывает другие проблемы ( см. этот вопрос для получения более подробной информации по проблеме).

Мне "вроде как" удалось изменить имя файла, как в следующем коде:

      @main
struct TestApp: App {
    
    @State var previousFileURL: String = ""
    
    var body: some Scene {
        DocumentGroup(newDocument: TestDocument()) { file in
            ContentView(document: file.$document)
                .onAppear() {
                    previousFileURL = file.fileURL!.path
                }
                .onDisappear() {
                    let newFileName = "TheNewFileName.testDocument"
                    let oldFileName = URL(fileURLWithPath: previousFileURL).lastPathComponent
                    
                    var newURL = URL(fileURLWithPath: previousFileURL).deletingLastPathComponent()
                    newURL.appendPathComponent(newFileName)
                        
                    do {
                        try FileManager.default.moveItem(atPath: oldURL.path, toPath: newURL.path)
                    } catch {
                        print("Error renaming file! Threw: \(error.localizedDescription)")
                    }                    
                }
        }
    }
}

Что он делает: он "сохраняет" начальный URL-адрес документа в переменной состояния сразу после инициализации представления (в ), назначив его в модификатор (я сделал это, потому что понятия не имею, как получить ссылку на прошло в закрытии). ТОГДА, используя модификатор, я использую 's для присвоения нового имени - просто "перемещая" файл с предыдущего URL-адреса на вновь сгенерированный (который, по сути, ДОЛЖЕН переименовать файл); предоставленный пример кода использует жестко запрограммированную строку , но в моем фактическом коде (это было бы слишком долго, чтобы публиковать здесь практически) я извлекаю это новое имя файла из значения, которое хранится в фактическом документе, которое, в свою очередь, является строкой, которую пользователь приложения может редактировать, когда документ открытый (имеет смысл?).

ВОПРОСЫ

В настоящее время это очень неприятная проблема: при определенных обстоятельствах (то есть, когда приложение только что запущено и новый документ был создан с помощью кнопки «плюс»), код ведет себя так, как я и ожидал - он открывает новый документ, в котором я могу (с помощью «представления содержимого») редактировать (и сохранять) строку, которая станет именем файла, и когда я «закрываю ее» (с помощью кнопки «Назад» в NavigationView), он обновляет подходящее имя файла, что я могу подтвердить, фактически просмотрев файл в браузере документов.

НО ... если я повторно открываю тот же файл или работаю с другим файлом, или просто выполняю весь процесс создания нового файла и т.д. снова БЕЗ закрытия приложения, то, по-видимому, как-то портится до точки, где операция фактически КОПИРУЕТ файл (с новым именем), но не удаляет и не переименовывает «старый», так что вы получаете два файла: один с новым именем и один со «старым» / предыдущим именем.

Это происходит ДАЖЕ, если я проверю наличие файла со старым именем: когда он попадает в эти условия, фактически находит предыдущий / старый файл, но при «перемещении» его к новому имени он затем копирует его, а не переименовывает. Странно, но я предполагаю, что это из-за (очевидных) ошибок, которые я упомянул в приведенной выше ссылке.

Надеюсь, это укажет кому-то с большим опытом и пониманием на лучший ответ, которым они (надеюсь) поделятся здесь.

Вы тестировали это "хакерское" решение на устройстве? Он хорошо работает на Simulator, но из-за новых правил разрешения доступа в iOS 13 код выдает “XXXXXX” couldn’t be moved because you don’t have permission to access “YYYYYY”.Так что да, очевидно, вы даже не можете переименовать файлы в папке, созданной приложением SwiftUI. Я еще не пробовал это в новом Swift UI (iOS14).

Вы тестировали вышеупомянутое "хакерское" решение на устройстве? Он хорошо работает на Simulator, но из-за новых правил разрешения доступа в iOS 13 код выдает “XXXXXX” couldn’t be moved because you don’t have permission to access “YYYYYY”.

Я покопался глубже и попробовал перезаписать стандартную init() и определения функций Document.swift стандартный код, сгенерированный XCode, чтобы установить желаемое имя файла на preferredFilename а также filename свойства :

      struct SomeDocument: FileDocument, Decodable, Encodable {
   
    static var readableContentTypes: [UTType] { [.SomeDocument] }
    
    var someData: SomeCodableDataType
    
    init() {
        self.someData = SomeCodableDataType()
        print("Creating.\n")
    }
    
    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents else {
            throw CocoaError(.fileReadCorruptFile)
        }
        let savedPreferredName = configuration.file.preferredFilename
        let savedName = configuration.file.preferredFilename
        let fileRep = try JSONDecoder().decode(Self.self, from: data)
        self.someData = fileRep.someData
        print("Loading.\n  Filename: \(savedPreferredName ?? "none") or \(savedName ?? "none")\n")
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        do {
            let fileRep = try JSONEncoder().encode(self)
            let fileWrapper = FileWrapper.init(regularFileWithContents: fileRep)
            fileWrapper.preferredFilename = fileName()
            fileWrapper.filename = fileName()
            print("Writing.\n  Filename \(fileWrapper.preferredFilename ?? "none") or \(fileWrapper.filename ?? "none").\n")
            return fileWrapper
        } catch {
            throw CocoaError(.fileReadCorruptFile)
        }
    }
    
    func fileName() -> String {
        
        let timeFormatter = DateFormatter()
        timeFormatter.dateFormat = "yyMMdd'-'HH:mm"
        let timeStamp = timeFormatter.string(from: Date())
        
        let extention = ".ext"
        let newFileName = timeStamp + "-\(someData.someUniqueValue())" + extention
        
        return newFileName
    }
}

Вот распечатка консоли. Я добавил действия пользователя в скобки []:

      [CREATE DOC BY TAPPING +]
Creating.
[AUTOMATIC WRITE]
Writing.
  Filename 210628-16:49-SomeUniqueValue.ext or 210628-16:49-SomeUniqueValue.ext.
[AUTOMATIC LOAD]
Loading.
  Filename: none or none
  FileURL: /Users/bora/Library/Developer/CoreSimulator/Devices/F126086A-A752-4A71-B589-1B37DFC02746/data/Containers/Data/Application/D81C9D76-7986-4C0D-BA2C-1FDF69703875/Documents/Untitled 2.ext
  isEditable: true
[CLOSING DOC]
Writing.
  Filename 210628-16:49-SomeUniqueValue.ext or 210628-16:49-SomeUniqueValue.ext.
  
[REOPENING DOC]
Loading.
  Filename: none or none
  FileURL: /Users/bora/Library/Developer/CoreSimulator/Devices/F126086A-A752-4A71-B589-1B37DFC02746/data/Containers/Data/Application/D81C9D76-7986-4C0D-BA2C-1FDF69703875/Documents/Untitled 2.ext
  isEditable: true

Итак, после первоначального создания документа первая запись (с func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper), имя файла присвоено правильно. Однако, когда код представления загружает документ, становится ясно, что ни одно из свойств имени файла не было использовано. То же самое повторяется, когда документ закрывается (запись с назначенными именами) и открывается снова.

Это похоже на ошибку. Я не понимаю, почему DocumetGroup не использует свойства имени файла, определенно используя содержимое данных, предоставленное тем же FileWrapper.

Я еще не пробовал это на новом SwiftUI (iOS14). Я сделаю и доложу.

ОБНОВЛЕНИЕ: Протестировано на iOS 14, там тоже не работает. Полагаю, пора радар.

Другие вопросы по тегам