swiftswiftuiuinavigationcontrollerquicklook

How to hide Title Menu on Navigationbar in iOS16


I'm using QuickLook and I did hide share button before, But on iOS 16, there is a new arrow within the title. it contents share button and save to file etc...

I want to hide the whole menu or all buttons

I didn't know how to hide it I tried to add

navigationController.navigationItem.titleMenuProvider = nil
navigationController.navigationItem.documentProperties = nil

But it doesn't work

enter image description here

My Current Code

class AppQLPreviewController: QLPreviewController {

var toolbars: [UIToolbar] = []

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // Hide Toolbar(share button) on iOS15
    navigationItem.rightBarButtonItems = [UIBarButtonItem(), UIBarButtonItem()]
    
    self.navigationController?.toolbar.isHidden = true
    for childVC in children {
        childVC.navigationController?.setToolbarHidden(true, animated: false)
    }
    
    toolbars = findToolbarsInSubviews(forView: view)
    for toolbar in toolbars {
        toolbar.isHidden = true
        toolbar.addObserver(self, forKeyPath: "hidden", options: .new, context: nil)
    }
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    navigationItem.rightBarButtonItems = [UIBarButtonItem(), UIBarButtonItem()]
    self.navigationController?.toolbar.isHidden = true
    for childVC in children {
        childVC.navigationController?.setToolbarHidden(true, animated: false)
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    for toolbar in toolbars {
        toolbar.removeObserver(self, forKeyPath: "hidden")
    }
}

override var shouldAutorotate: Bool {
    return true
}


private func findToolbarsInSubviews(forView view: UIView) -> [UIToolbar] {
    var toolbars: [UIToolbar] = []
    for subview in view.subviews {
        if subview is UIToolbar {
            toolbars.append(subview as! UIToolbar)
        }
        toolbars.append(contentsOf: findToolbarsInSubviews(forView: subview))
    }
    return toolbars
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    if let keyPath = keyPath,
        let toolbar = object as? UIToolbar,
        let value = change?[.newKey] as? Bool,
        keyPath == "hidden" && value == false {
        toolbar.isHidden = true
    }
}

}

QuickLook UIViewControllerRepresentable

import SwiftUI
 import QuickLook

 struct QuickLook: UIViewControllerRepresentable {
    
    let urls: [URL]
    @Binding var isPresented: Bool
    func makeUIViewController(context: Context) -> UINavigationController {
        
        let controller = AppQLPreviewController()
        controller.dataSource = context.coordinator

        controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: context.coordinator, action: #selector(context.coordinator.dismiss))
        let navigationController = UINavigationController(rootViewController: controller)
            
        navigationController.navigationBar.topItem?.titleMenuProvider = nil
        navigationController.navigationBar.topItem?.documentProperties = nil

        return navigationController
    }
    
    func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self)
    }
    
    class Coordinator: QLPreviewControllerDataSource {
        let parent: QuickLook
        
        init(parent: QuickLook) {
            self.parent = parent
        }
        
        func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
            if !parent.urls.isEmpty {
                
                if parent.urls.count > 1 {
                    return parent.urls.count
                } else {
                    return 1
                }
            }
            return 0
        }
        
        func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
                if parent.urls.count > 1 {
                    return parent.urls[index] as QLPreviewItem
                }
                else {
                    return parent.urls.first! as QLPreviewItem
                }
            
           
        }
        
        @objc func dismiss() {
            self.parent.isPresented = false
        }
        
    }
}

My View

struct ContentView: View {
@State private var openQuickLook = false

@State private var urls: [URL] = [(Bundle.main.url(forResource: "cat1", withExtension: "jpg")!), (Bundle.main.url(forResource: "cat2", withExtension: "jpg")!)]

var body: some View {
    VStack {
        Button {
                openQuickLook.toggle()
        } label: {
            Text("Open Quick Look")
        }

    }

    .padding()
    .fullScreenCover(isPresented: $openQuickLook) {
        QuickLook(urls: urls, isPresented: $openQuickLook)
            .ignoresSafeArea()
    }
}

}

The current result is

enter image description here

My code hide share button on toolbar but on iOS 16 it appear on Title Menu with save to File and Photos, I want to hide Title Menu and share button, so the quick look only show image and title without the menu button

I tried also add this code inside viewWillAppear

            if let subViews = self.view.subviews.first?.subviews.compactMap({$0 as? UINavigationBar}) {
            if let navBar = subViews.first {
                DispatchQueue.main.async {
                    if #available(iOS 16.0, *) {
                        navBar.topItem?.titleMenuProvider = nil
                        navBar.topItem?.documentProperties = nil
                    } else {
                        // Fallback on earlier versions
                    }
                }
            }
        }

but nothing change title menu still appeared


Solution

  • Try this (a hacky workaround):

    let preview = QLPreviewController()
    preview.dataSource = self
    self.present(preview, animated: true) {
        if let subViews = preview.view.subviews.first?.subviews.compactMap({$0 as? UINavigationBar}) {
            if let navBar = subViews.first {
                DispatchQueue.main.async {
                    if #available(iOS 16.0, *) {
                        navBar.topItem?.titleMenuProvider = nil
                        navBar.topItem?.documentProperties = nil
                    } else {
                        // Fallback on earlier versions
                    }
                }
            }
        }
    }
    

    Result:

    enter image description here


    2nd Approach (After OP's updated the question):

    Try executing the code after some delay, this should work. Add the below code in the AppQLPreviewController class:

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
            print("check")
            
            self.navigationController?.navigationBar.topItem?.documentProperties = nil
            self.navigationController?.navigationBar.topItem?.titleMenuProvider = nil
        })
    }