Как создать приложение SwiftUI DocumentGroup, не запуская средство выбора файлов?

Если пользователь использует шаблон приложения документа в Xcode для создания приложения SwiftUI, macOS запускает его с нового документа. Это хорошо. Я могу работать с этим, чтобы представить интерфейс пользователя в новом документе.

Однако с тем же приложением, работающим на iOS, пользователь вместо этого приветствует стандартного контроллера представления документа для создания или выбора документа.

Это бесполезно, потому что у меня нет возможности предоставить информацию о подключении или другую настраиваемую информацию.

Я заметил, что если вы добавите WindowGroup к Scene, приложение отобразит эту группу окон. Но тогда я не знаю, как вызвать пользователя в пользовательский интерфейс средства выбора.

Кто-нибудь придумал, как делать такие вещи, как презентация на борту поверх этого приложения на основе DocumentGroup?

Вот полное приложение для документов

import SwiftUI
import UniformTypeIdentifiers

@main
struct DocumentTestApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: DocumentTestDocument()) { file in
            ContentView(document: file.$document)
        }
    }
}

struct ContentView: View {
    @Binding var document: DocumentTestDocument

    var body: some View {
        TextEditor(text: $document.text)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(document: .constant(DocumentTestDocument()))
    }
}

extension UTType {
    static var exampleText: UTType {
        UTType(importedAs: "com.example.plain-text")
    }
}

struct DocumentTestDocument: FileDocument {
    var text: String

    init(text: String = "Hello, world!") {
        self.text = text
    }

    static var readableContentTypes: [UTType] { [.exampleText] }

    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents,
              let string = String(data: data, encoding: .utf8)
        else {
            throw CocoaError(.fileReadCorruptFile)
        }
        text = string
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = text.data(using: .utf8)!
        return .init(regularFileWithContents: data)
    }
}

3 ответа

Хорошо, друзья, вот хороший и хитрый способ заставить все работать, добраться до ключевых окон и настроить onboarding/paywall/все, что вы хотите!

      import SwiftUI

@main
struct ExampleApp: App {
    @StateObject var captain = Captain()
    
    var body: some Scene {
        DocumentGroup(newDocument: ExampleOfDocumentGroupAndOnboardingPaywallDocument()) { file in
            ContentView(document: file.$document)
        }
    }
}

class Captain: ObservableObject {
    var onboardingSheet: UIViewController?
    @objc func loadData() {
        onboardingSheet = try? OnboardingOrPaywall(dismissHandler: dismissSheet).presentFromDocumentGroup()
    }
    
    func dismissSheet() {
        onboardingSheet?.dismiss(animated: true)
    }
    
    init() {
        NotificationCenter.default.addObserver(self,
                                                      selector: #selector(loadData),
                                                      name: UIApplication.didBecomeActiveNotification,
                                                      object: nil)
    }
}

public protocol DocumentGroupSheet: View {}

struct OnboardingOrPaywall: DocumentGroupSheet {
    var dismissHandler: () -> Void
    var body: some View {
        Button("Done") {
            dismissHandler()
        }
        Text("Let me introduce you to this delicious app!")
    }
}

public enum DocumentGroupSheetError: Error {
    
    case noParentWindow
}

public extension DocumentGroupSheet {
    
    func presentFromDocumentGroup() throws -> UIViewController  {
        let window = UIApplication.shared.activeKeyWindows.first
        let parent = window?.rootViewController
        guard let parent = parent else { throw DocumentGroupSheetError.noParentWindow }
        let sheet = UIHostingController(rootView: body)
        sheet.modalPresentationStyle = .fullScreen
        parent.present(sheet, animated: false, completion: nil)
        return sheet
    }
}

public extension UIApplication {
    
    var activeWindowScenes: [UIWindowScene] {
        connectedScenes
            .filter { $0.activationState == .foregroundActive }
            .compactMap { $0 as? UIWindowScene }
    }
    
    var activeWindows: [UIWindow] {
        activeWindowScenes
            .flatMap { $0.windows }
    }

    var activeKeyWindows: [UIWindow] {
        activeWindows
            .filter { $0.isKeyWindow }
    }
}

Похоже, это теперь возможно начиная с iOS 16 с документацией здесь .

      @main
struct Mail: App {
    var body: some Scene {
        WindowGroup(id: "mail-viewer") {
            MailViewer()
        }
    }
}


struct NewViewerButton: View {
    @Environment(\.openWindow) private var openWindow


    var body: some View {
        Button("Open new mail viewer") {
            openWindow(id: "mail-viewer")
        }
    }
}

Итак, когда появится сцена, вы можете открыть окно регистрации.

Приложение по умолчанию показывает первую сцену окна, поэтому поместите сначала сцену окна, а затем DocumentGroup. Где-то в конце процесса посадки (успешный путь) вызовите контроллер документов для создания нового документа (DocumentGroup основан на том же механизме NSDocumentController внутри).

Обновление: ниже для macOS

* только что узнал, что исходный вопрос для iOS

Таким образом, возможный подход

      @main
struct DocumentTestApp: App {
    var body: some Scene {
        WindowGroup("On-Boarding") {
          // ContentView()

          // In some action at the end of this scene flow
          // just close current window and open new document
          Button("Demo") {
            NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: nil, from: nil)
            NSDocumentController.shared.newDocument(nil)
          }
        }

        DocumentGroup(newDocument: DocumentTestDocument()) { file in
            ContentView(document: file.$document)
        }
    }
}
Другие вопросы по тегам