Swift UI 2 DocumentGroup get navigation bar buttons actions

1.5k views Asked by At

enter image description here

I'm using the new 'DocumentGroup' Scene for an iOS 14 (currently working on Beta) project and I really don't know how can I detect events in the navigation bar like pressing '<' or back (see screen)of the predefined Navigation bar of the 'DoucmentGroup' Scene. For custom buttons it's possible and it's also not a big deal to change the style of the bar(like gray). I tried to disable the button, adding new buttons to the navigation bar etc. The following example is the standard code when you are going to create a new document based app in Xcode:

@main struct DocumentAppApp: App {
@SceneBuilder var body: some Scene {
    
    // Adding of a new document
    DocumentGroup(newDocument: DocumentAppDocuments()) { file in
        ContentView(document: file.$document) // .navigationBarBackButtonHidden(true)
    }
}

}

struct ContentView: View {
@Binding var document: DocumentAppDocuments

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

}

3

There are 3 answers

0
Procrastin8 On

I ended up doing the following in my document's ContentView.body:

        NavigationView {
            SideBar(document: $document)
                .navigationBarItems(leading: Button("Back") {
                    // figure out how to programmatically go back...
                })
            
            Text("Nothing selected")
        }
        .navigationBarHidden(true)

I essentially killed the automatic NavigationView that comes with the document browser. This is not ideal for many reasons, the first of which is that I haven't actually figured out how to go back just yet. If we had access to the underlying NavigationView so i could set up my sidebar and my content, this wouldn't be a problem, but so far I haven't been successful here, either.

DocumentGroup kind of seems like half-baked garbage though. Why is it pushing, instead of presenting over an "empty" document? Why can't i set the accent color of the DocumentGroup, etc. I've written feedback for these items but who knows if it will get addressed before release.

The last option is to take this over yourself and not use DocumentGroup at all, use UIDocumentBrowserViewController as a UIViewControllerRepresentable and move on with your life. I will probably end up here soon.

UPDATE: I ditched DocumentGroup. It just doesn't make any sense. Pushing the content? Dumb. Changing tint color? Can't. My app has also gotten rid of the SwiftUI app entry so I can fully control the UIDocumentBrowserViewController, and I suggest you do the same. The only tough part was figuring out how to best utilize UIDocument based storage instead of the nicer FileDocument SwiftUI provides. To do that, I basically did this:

final class Document: UIDocument, ObservableObject {

   var someDocumentProperty: String = "" {
     willSet {
       // this updates the SwiftUI view hierarchy
       objectWillChange.send()
     } didSet {
       // this will autosave whenever you change this property
       updateChangeCount(.done)
     }
   }
}

Now it will fully work with SwiftUI views, save properly, everything.

1
Jason Cardwell On

Exact same problem. In a nutshell, I decided to see if there was still UIApplication.shared being created, and when there was I was able to programmatically go back like so:

private func goBack() {
#if canImport(UIKit)
guard let currentWindow = UIApplication.shared.windows.first,
      let documentBrowser = currentWindow
      .rootViewController as? UIDocumentBrowserViewController
else
{
  logw(
    "<\(#fileID) \(#function)> Failed to retrieve document browser."
  )
  return
}

documentBrowser.dismiss(animated: true) {
  logi("<\(#fileID) \(#function)> the document browser has dismissed it's child.")
}

#endif

}

or simplified:

`UIApplication.shared.windows.first?.rootViewController?.dismiss(animated: true) { print("the document browser has dismissed it's child.") }`
0
Vilém Kurz On

My goal is to inject own functionality, while keeping the look and feel of the system back button, provided by the DocumentGroup. Please note, that my document view is UIViewController, wrapped in UIViewControllerRepresentable. The solution, based on Jason Cardwell's:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    var systemBackButtonItem = navigationController?.navigationBar.items?[0].leftBarButtonItems?[0]
    systemBackButtonItem?.target = self
    systemBackButtonItem?.action = #selector(done)
}

@objc private func done() {
    UIApplication.shared.windows.first?.rootViewController?.dismiss(animated: true) { 
        print("the document browser has dismissed it's child.") 
    }
}

Be cautious though, this might clash with some functionality provided by DocumentGroup. However, so far so good in my case. Another option:

    private weak var originalBackButtonTarget: AnyObject?
    private var originalBackButtonAction: Selector?

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        var systemBackButtonItem = navigationController?.navigationBar.items?[0].leftBarButtonItems?[0]
        originalBackButtonTarget = systemBackButtonItem?.target
        systemBackButtonItem?.target = self
        originalBackButtonAction = systemBackButtonItem?.action
        systemBackButtonItem?.action = #selector(done)
    }

    @objc private func done() {
        guard let originalBackButtonAction = originalBackButtonAction else { return }
        originalBackButtonTarget?.perform(originalBackButtonAction, with: self)
    }