I am subclassing QLPreviewController in my application and using the following code.
QLPreviewControllerSubClass* preview = [[QLPreviewControllerSubClass alloc] init];
[self presentViewController:preview
animated:YES
completion:^()
{
// do more stuff here
}];
I want to hide the right bar button .Tried
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self navigationItem].rightBarButtonItems = nil;
}
But its not hiding.Any help will be appreciable
I'm dealing the same problem also.
I made the rightBarButton hidden, but may have some problem when the loading of pdf lasts for a long time.
Below is my process.
1.Make a sub class of QLPreviewController.
2.Add a timer to repeats setting the rightBarButton to nil when the class init.
_hideRightBarButtonTimmer = [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(hideRightButton)
userInfo:nil
repeats:YES];
3.Invalidate the timer in viewDidAppear.
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(cancelTimmer) userInfo:nil repeats:NO];
And I found the rightBarButton is setup while the loading of pdf file is finished. If we can detect the event the solution will be much easier and clearer.
Hopes it will be helpful.
Its simple and working well import quicklook and create a class of QL-Click here for Result which u all need, working well
import UIKit
import QuickLook.
class QLSubclass: QLPreviewController , QLPreviewControllerDataSource {
var p = NSURL()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLayoutSubviews() {
navigationItem.rightBarButtonItems?[0] = UIBarButtonItem()
}
func show(controller: UIViewController, url: NSURL) {
// Refreshing the view
p = url
self.dataSource = self
self.reloadData()
// Printing the doc
if let navController = controller.navigationController {
navController.pushViewController(self, animated: true)
}
else {
controller.show(self, sender: nil)
}
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
let doc = p
return doc
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
}
after then in your view controller class-
class PatternVC: UIViewController, UIDocumentInteractionControllerDelegate , UINavigationControllerDelegate {
var link = ""
override func viewDidLoad() {
super.viewDidLoad()
let url = "YOUR_URL_LINK"
//check file exist in local or not------
if isFileExist(imgURL: url) {
//Found then show
let loadPath = loadDataFromDirectory(imgURL: url)
showFileWithPath(path: loadPath)
}else {
//save in local
saveDataOnDocumentDirectory(imgURL: url)
}
}
override func viewDidLayoutSubviews() {
self.navigationController?.setNavigationBarHidden(true, animated: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}//Open data
func showFileWithPath(path: String){
let isFileFound:Bool? = FileManager.default.fileExists(atPath: path)
if isFileFound == true{
QLSubclass().show(controller: self, url: URL(fileURLWithPath: path) as NSURL)
}else{}}
Also add a function for save in local in your view controller-
//downloading request-----
private var downloadTask: URLSessionDownloadTask?
func saveDataOnDocumentDirectory(imgURL: String) {
//let url = URL(string: imgURL)
let escapedAddress = imgURL.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let url2 = URL(string: escapedAddress!)
let sessionConfig = URLSessionConfiguration.default
//let sessionConfig = URLSessionConfiguration.background(withIdentifier: "backgroundSession")
let session: URLSession! = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
downloadTask = session.downloadTask(with: url2!)
downloadTask?.resume()
DispatchQueue.main.async {
}
}}
After that includes a extension on view extension PatternVC: URLSessionDelegate, URLSessionDownloadDelegate{
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if error != nil {
// lbl.text = "Download failed"
}
resetView()
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
let fileManager = FileManager.default
do {
let requestURL = downloadTask.currentRequest?.url
let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:true)
let fileURL = documentDirectory.appendingPathComponent(getImageNameFromUrl(imgURL: requestURL!))
do {
try fileManager.moveItem(at: location, to: fileURL)
print("save item: \(fileURL)")
} catch (let writeError) {
print("Error creating a file \(fileURL) : \(writeError)")
}
} catch {
print(error)
}
DispatchQueue.main.async {
//self.loadDirectory()
self.resetView()
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// 1
//guard let _ = downloadTask.originalRequest?.url, let download = model?.imagePath else { return }
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
DispatchQueue.main.async {
//self.radialView.ringProgress = CGFloat(progress * 100)
}
print("-URL: \(downloadTask.currentRequest?.url?.relativePath ?? "") ----Per: \(CGFloat(progress * 100))")
}
func resetView() {
downloadTask!.cancel()
}
func isFileExist(imgURL: String) -> Bool{
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let escapedAddress = imgURL.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let url2 = URL(string: escapedAddress!)
if let pathComponent = url.appendingPathComponent(getImageNameFromUrl(imgURL: url2!)) {
let filePath = pathComponent.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("FILE AVAILABLE")
return true
} else {
print("FILE NOT AVAILABLE")
let url = APIConstant.videoJpgUrl + link
//self.loadUrl (urlString:url)
return false
}
} else {
print("FILE PATH NOT AVAILABLE")
return false
}
}
func loadDataFromDirectory(imgURL: String) -> String
{
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let escapedAddress = imgURL.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let url2 = URL(string: escapedAddress!)
if let pathComponent = url.appendingPathComponent(getImageNameFromUrl(imgURL: url2!)) {
let filePath = pathComponent.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("FILE AVAILABLE")
return filePath
} else {
print("FILE NOT AVAILABLE")
let url = APIConstant.videoJpgUrl + link
self.loadUrl (urlString:url)
return filePath
}
}else
{
return "ERROO"
}
}
func getImageNameFromUrl(imgURL: URL) -> String{
let name = imgURL.lastPathComponent
let result = name.substring(from: name.index(name.startIndex, offsetBy: 5))
return result
}
I have found a solution to disable (not hide) therightBarButtonItem in QLPreviewController
The solution works fine for me in iOS8 and iOS9
You simply need to subclass QLPreviewController and override the following methods, then use your subclass instead of the original QLPreviewController
- (void)viewDidLoad {
[super viewDidLoad];
// When coming back from background we make sure the share button on the rightbBarButtonItem is disabled
__weak typeof(self) weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
weakSelf.navigationItem.rightBarButtonItem.enabled = NO;
}];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationItem.rightBarButtonItem.enabled = NO; // Disable the share button
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.navigationItem.rightBarButtonItem.enabled = NO; // Disable the share button
}
Tested on iOS 9 with swift.
Recently had to solve this issue and had trouble finding a way to hide this dang button. I finally managed to hide the right share button using an answer from this stackoverflow post.
Basically, you want to subclass QLPreviewController and call the inspectSubviewForView() function in your viewWillAppear() function. Once you find the navigation item containing the share button, you can remove it:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// ** traverse subviews until we find the share button ** //
inspectSubviewForView(self.view)
}
func inspectSubviewForView(view: UIView) {
for subview in view.subviews {
if subview is UINavigationBar {
// ** Found a Nav bar, check for navigation items. ** //
let bar = subview as! UINavigationBar
if bar.items?.count > 0 {
if let navItem = bar.items?[0] {
// ** Found the share button, hide it! ** //
hideRightBarItem(navItem)
}
}
}
if subview.subviews.count > 0 {
// ** this subview has more subviews! Inspect them! ** //
inspectSubviewForView(subview)
}
}
}
func hideRightBarItem(navigationItem: UINavigationItem) {
// ** Hide/Remove the Share button ** //
navigationItem.setRightBarButtonItem(nil, animated: false)
}
The previous poster from the above link warned that this may not get through apple's review process as you are accessing private APIs, so use at your own risk! Also if Apple updates the QLPreviewController in later versions of iOS this code may no longer work.
Still, this is the only solution that has worked for me. I hope it works for you too!
Thanks to Matthew Kostelecky, i was able to hide the share button but i would like to add some details for those need this in universal apps with multiple files.
Firstly Matthew's answer works if you use QLPreviewController modally.
If you push QLPreviewController from your navigation controller like this;
navigationController?.pushViewController(quickLookController, animated: true)
It wont be able to find any NavigationBar or ToolBar.
You should call QLPreviewController modally like this;
presentViewController(quickLookController, animated: true, completion: nil)
Also if you are developing universal app and you have list of files to play. There will be another button(List Button);
In iPhones if you have multiple files, QLPreviewController will create toolbar to show "List Button" and "Share Button" and both of these will be on ToolBar.
In iPads both of these buttons are on NavigationBar and there is no ToolBar.
So addition to Matthew's answer you should search toolBar if you have multiple files in iphone;
func inspectSubviewForView(view: UIView) {
for subview in view.subviews {
if subview is UINavigationBar {
// ** Found a Nav bar, check for navigation items. ** //
let bar = subview as! UINavigationBar
if bar.items?.count > 0 {
if let navItem = bar.items?[0] {
navItem.setRightBarButtonItem(nil, animated: false)
}
}
}
if subview is UIToolbar {
// ** Found a Tool bar, check for ToolBar items. ** //
let bar = subview as! UIToolbar
if bar.items?.count > 0 {
if let toolBarItem = bar.items?[0] {
toolBarItem.enabled = false
}
}
}
if subview.subviews.count > 0 {
// ** this subview has more subviews! Inspect them! ** //
inspectSubviewForView(subview)
}
}
}
This piece of code will hide share button on iPad and disable share button on iPhone.
Hope it helps to those still need it.
Simple solution for this is add one dummy view to current viewController and Add QLPreviewControlle.view to dummy view .
previewController = [[QLPreviewController alloc] init];
previewController.dataSource = self;
previewController.delegate = self;
previewController.currentPreviewItemIndex = 0;
[self.ContentView addSubview:previewController.view];
- (IBAction)removeQPView:(id)sender {
[previewController.view removeFromSuperview];
}
Another way to achieve this is by subclassing UIToolbar and overriding setItems(_:animated:). You can return an empty array if you want to remove all or return only buttons you want to keep. Here is an example PreviewControllerHideBottomButtons
Go for subclassing the QLPreviewController and use the below-mentioned code, that only works fine for me using Xcode 8.3.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let firstView = self.childViewControllers.first {
for view in firstView.view.subviews {
if view.isKind(of: UIToolbar.self) {
view.removeFromSuperview()
}
}
}
}
Related
In my application, I'm using the QuickLook framework to view the document files such as pdf, ppt, doc, etc. etc. But due to privacy concerns, I don't want that the user can share this document with others so please let me know how to disable/hide the share button and also the copy-paste option.
I know this question can be asked by a number of times and tried many solutions but nothing works for me
hide share button from QLPreviewController
UIDocumentInteractionController remove Actions Menu
How to hide share button in QLPreviewController using swift?
Hide right button n QLPreviewController?
Please suggest to me to achieve this.
Here is my demo code:
import UIKit
import QuickLook
class ViewController: UIViewController {
lazy var previewItem = NSURL()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func displayLocalFile(_ sender: UIButton){
let previewController = QLPreviewController()
// Set the preview item to display
self.previewItem = self.getPreviewItem(withName: "samplePDf.pdf")
previewController.dataSource = self
self.present(previewController, animated: true, completion: nil)
}
#IBAction func displayFileFromUrl(_ sender: UIButton){
// Download file
self.downloadfile(completion: {(success, fileLocationURL) in
if success {
// Set the preview item to display======
self.previewItem = fileLocationURL! as NSURL
// Display file
let previewController = QLPreviewController()
previewController.dataSource = self
self.present(previewController, animated: true, completion: nil)
}else{
debugPrint("File can't be downloaded")
}
})
}
func getPreviewItem(withName name: String) -> NSURL{
// Code to diplay file from the app bundle
let file = name.components(separatedBy: ".")
let path = Bundle.main.path(forResource: file.first!, ofType: file.last!)
let url = NSURL(fileURLWithPath: path!)
return url
}
func downloadfile(completion: #escaping (_ success: Bool,_ fileLocation: URL?) -> Void){
let itemUrl = URL(string: "https://images.apple.com/environment/pdf/Apple_Environmental_Responsibility_Report_2017.pdf")
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent("filename.pdf")
// to check if it exists before downloading it
if FileManager.default.fileExists(atPath: destinationUrl.path) {
debugPrint("The file already exists at path")
completion(true, destinationUrl)
// if the file doesn't exist
} else {
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: itemUrl!, completionHandler: { (location, response, error) -> Void in
guard let tempLocation = location, error == nil else { return }
do {
// after downloading your file you need to move it to your destination url
try FileManager.default.moveItem(at: tempLocation, to: destinationUrl)
print("File moved to documents folder")
completion(true, destinationUrl)
} catch let error as NSError {
print(error.localizedDescription)
completion(false, nil)
}
}).resume()
}
}
}
//MARK:- QLPreviewController Datasource
extension ViewController: QLPreviewControllerDataSource {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
controller.navigationItem.rightBarButtonItem = nil
return self.previewItem as QLPreviewItem
}
}
Please provide your suggestion to do so or any other framework to view different file formats.
Here is the image
Find below adopted my approach to your code (with modifications to test locally, but the code should be clear). The idea is
a) to override, which is completely allowed by API, needed classes to intercept modification
b) to use intentionally own UINavigationController, as only one navigation controller can be in stack
So here is code:
// Custom navigation item that just blocks adding right items
class MyUINavigationItem: UINavigationItem {
override func setRightBarButtonItems(_ items: [UIBarButtonItem]?, animated: Bool) {
// forbidden to add anything to right
}
}
// custom preview controller that provides own navigation item
class MyQLPreviewController: QLPreviewController {
private let item = MyUINavigationItem(title: "")
override var navigationItem: UINavigationItem {
get { return item }
}
}
class MyViewController : UIViewController, QLPreviewControllerDataSource {
lazy var previewItem = NSURL()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
// just stub testing code
let button = UIButton(type: .roundedRect)
button.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
button.setTitle("Show", for: .normal)
button.addTarget(self, action:
#selector(displayLocalFile(_:)), for: .touchDown)
view.addSubview(button)
self.view = view
}
#objc func displayLocalFile(_ sender: UIButton){
let previewController = MyQLPreviewController() // << custom preview
// now navigation item is fully customizable
previewController.navigationItem.title = "samplePDF.pdf"
previewController.navigationItem.leftBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .done, target: self,
action: #selector(closePreview(_:)))
// wrap it into navigation controller
let navigationController = UINavigationController(rootViewController: previewController)
// Set the preview item to display
self.previewItem = self.getPreviewItem(withName: "samplePDF.pdf")
previewController.dataSource = self
// present navigation controller with preview
self.present(navigationController, animated: true, completion: nil)
}
#objc func closePreview(_ sender: Any?) {
self.dismiss(animated: true) // << dismiss preview
}
func getPreviewItem(withName name: String) -> NSURL{
// Code to diplay file from the app bundle
let file = name.components(separatedBy: ".")
let path = Bundle(for: type(of: self)).path(forResource: file.first!, ofType: file.last!)
let url = NSURL(fileURLWithPath: path!)
return url
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return self.previewItem as QLPreviewItem
}
}
I am using UIDocumentBrowser to retrieve files. But I am not able to place a back or cancel button in the navigation bar.
I want to make a cancellation button for this but I can't make a cancellation button. How can I solve this problem?
current code
import Foundation
import UIKit
#available(iOS 11.0, *)
class DocumentBrowserViewController : UIDocumentBrowserViewController, UIDocumentBrowserViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
browserUserInterfaceStyle = .dark
view.tintColor = .white
}
func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: #escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Void) {
let newDocumentURL: URL? = nil
// Set the URL for the new document here. Optionally, you can present a template chooser before calling the importHandler.
// Make sure the importHandler is always called, even if the user cancels the creation request.
if newDocumentURL != nil {
importHandler(newDocumentURL, .move)
} else {
importHandler(nil, .none)
}
}
func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentURLs documentURLs: [URL]) {
guard let sourceURL = documentURLs.first else { return }
do{
try presentDocument(at: sourceURL)
} catch {
Log.Debug("\(error)")
}
}
func documentBrowser(_ controller: UIDocumentBrowserViewController, didImportDocumentAt sourceURL: URL, toDestinationURL destinationURL: URL) {
// Present the Document View Controller for the new newly created document
do{
try presentDocument(at: sourceURL)
} catch {
Log.Debug("\(error)")
}
}
func documentBrowser(_ controller: UIDocumentBrowserViewController, failedToImportDocumentAt documentURL: URL, error: Error?) {
// Make sure to handle the failed import appropriately, e.g., by presenting an error message to the user.
}
func presentDocument(at documentURL: URL) throws {
guard documentURL.startAccessingSecurityScopedResource() else {
throw IXError.fileAcessFailed
}
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let documentViewController = storyBoard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
documentViewController.document = Document(fileURL: documentURL)
}
}
picture of cancellation button that I want
Help me a lot
Thanks in advance.
Do I understand correctly that you want to push a viewController (documentViewController) on the navigation stack and have a back button on the navigationBar that leads you back to your main viewController (DocumentBrowserViewController)? If so first you need to push documentViewController on the current navigation stack.
First of all, does the documentViewController appears?
What I see is that you instantiate a documentViewController, set it's document to Document(...) and end of story. I don't use storyboard but does instantiate presents the viewController?
If you provide more details I will update the answer. But general conclusion is in your presentDocument(...), you need:
self.navigationController?.pushViewController(documentViewController, animated: true)
I learned about the UIDocumentBrowserViewController class and succeeded in adding buttons. But the position of the button is not where I want it to be.
But this has solved my fundamental problem, so I'll end the question.
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
allowsDocumentCreation = false
allowsPickingMultipleItems = false
browserUserInterfaceStyle = .dark
view.tintColor = .white
let cancelbutton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(cancelButton(sender:)))
additionalTrailingNavigationBarButtonItems = [cancelbutton]
}
#objc func cancelButton(sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
}
I have created a program that loads PDF files. I want when the user is editing that they can publish the file anywhere in PDF format.
Do I use UIDocumentInteractionController or use UIActivityViewController?
Here is the code:
import UIKit
import PDFKit
#available(iOS 11.0, *)
#available(iOS 11.0, *)
class PDFViewControllerEN: UIViewController {
var document: UIDocumentInteractionController!
override func viewDidLoad() {
super.viewDidLoad()
// retrieve URL to file in main bundle`
}
#IBOutlet var pdfview: UIView!
#IBAction func share(_ sender: UIButton) {
}
#IBAction func doAction2(_ sender: UIBarButtonItem) {
document.presentOptionsMenu(from: view.bounds, in: view, animated: true)
}
override func viewWillAppear(_ animated: Bool) {
//Here you are going to display your PdfController
//PDFController that is seprate class you had created to show pdf file being opened
//i.e
//check which button was being selected
switch ButtonSelected.Tag {
case 0:
var document: UIDocumentInteractionController = {
let pdfView = PDFView(frame: UIScreen.main.bounds)
let url = Bundle.main.url(forResource: "EN1", withExtension: "pdf")
let vc = UIDocumentInteractionController(url: url!)
pdfView.document = PDFDocument(url: url!)
view.addSubview(pdfView)
vc.delegate = self
return vc
}()
// document.presentPreview(animated: true)
break
case 1:
//here control when you selected button with tag 0
//here need to open pdf AR2
//set Frame here all bounds
var document: UIDocumentInteractionController = {
let pdfView = PDFView(frame: UIScreen.main.bounds)
let url = Bundle.main.url(forResource: "EN2", withExtension: "pdf")
let vc = UIDocumentInteractionController(url: url!)
pdfView.document = PDFDocument(url: url!)
view.addSubview(pdfView)
vc.delegate = self
return vc
}()
break
case 2:
//here control when you selected button with tag 0
//here need to open pdf AR2
//set Frame here all bounds
var document: UIDocumentInteractionController = {
let pdfView = PDFView(frame: UIScreen.main.bounds)
let url = Bundle.main.url(forResource: "EN3", withExtension: "pdf")
let vc = UIDocumentInteractionController(url: url!)
pdfView.document = PDFDocument(url: url!)
view.addSubview(pdfView)
vc.delegate = self
return vc
}()
break
default:
//Error Case
print("No tag Value Available")
}
}
}
#available(iOS 11.0, *)
extension PDFViewControllerEN: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
}
I have tried and explained in detail about UIDocumentInteractionController. Refer this link if you want to check the details.
https://medium.com/if-let-swift-programming/managing-files-in-ios-dfcdfdc1f426
Code
extension ViewController {
/// This function will set all the required properties, and then provide a preview for the document
func share(url: URL) {
documentInteractionController.url = url
documentInteractionController.uti = url.typeIdentifier ?? "public.data, public.content"
documentInteractionController.name = url.localizedName ?? url.lastPathComponent
documentInteractionController.presentPreview(animated: true)
}
/// This function will store your document to some temporary URL and then provide sharing, copying, printing, saving options to the user
func storeAndShare(withURLString: String) {
guard let url = URL(string: withURLString) else { return }
/// START YOUR ACTIVITY INDICATOR HERE
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
let tmpURL = FileManager.default.temporaryDirectory
.appendingPathComponent(response?.suggestedFilename ?? "fileName.png")
do {
try data.write(to: tmpURL)
} catch {
print(error)
}
DispatchQueue.main.async {
/// STOP YOUR ACTIVITY INDICATOR HERE
self.share(url: tmpURL)
}
}.resume()
}
}
extension ViewController: UIDocumentInteractionControllerDelegate {
/// If presenting atop a navigation stack, provide the navigation controller in order to animate in a manner consistent with the rest of the platform
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
guard let navVC = self.navigationController else {
return self
}
return navVC
}
}
extension URL {
var typeIdentifier: String? {
return (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier
}
var localizedName: String? {
return (try? resourceValues(forKeys: [.localizedNameKey]))?.localizedName
}
}
Calling
#IBAction func showOptionsTapped(_ sender: UIButton) {
/// Passing the remote URL of the file, to be stored and then opted with mutliple actions for the user to perform
storeAndShare(withURLString: "https://images5.alphacoders.com/581/581655.jpg")
}
Note
https://www.bignerdranch.com/blog/working-with-the-files-app-in-ios-11/
Before your files can appear in the Files app, you must indicate that your app supports Open in Place and File Sharing Enabled. These options are configured using keys in your Info.plist file.
The first key is UIFileSharingEnabled, which enables iTunes sharing of files in your Documents folder.
The second key is LSSupportsOpeningDocumentsInPlace, which grants the local file provider access to files in your Documents folder.
Add these keys to your Info.plist and set their values to YES.
Using UIDocumentInteractionController is quite easy. You just need to know the url of your file, then you present the menu:
/// Needs to be global, otherwise the controller will be destroyed when the file is handed over to target application
var documentInteractionController: UIDocumentInteractionController!
class MyViewController: UIViewController {
var url: URL
...
#IBAction func share(_ sender: UIBarButtonItem) {
documentInteractionController = UIDocumentInteractionController()
documentInteractionController.url = url
documentInteractionController.uti = url.uti
documentInteractionController.presentOptionsMenu(from: sender, animated: true)
}
}
extension URL {
var uti: String {
return (try? self.resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier ?? "public.data"
}
}
Silly case but might help to someone.
To anyone who can't save files check if your controller is alive. My problem was that my UIDocumentInteractionController was destroyed after it was closed.
Here's how my function looked like:
private func showDocumentInteractionController(url: URL) {
let documentInteractionController = UIDocumentInteractionController(url: url)
documentInteractionController.presentOptionsMenu(from: view.frame, in: view, animated: true)
documentInteractionController.delegate = self
}
The fix is to make sure that UIDocumentInteractionController is alive after it closes:
class ViewController: UIViewController, UIDocumentInteractionControllerDelegate {
private let documentInteractionController = UIDocumentInteractionController()
private func showDocumentInteractionController(url: URL) {
documentInteractionController.url = url
documentInteractionController.presentOptionsMenu(from: view.frame, in: view, animated: true)
documentInteractionController.delegate = self
}
}
This should work which shows a PDF file:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
guard let path = Bundle.main.url(forResource: "YOUR_PDF_FILE_NAME_WITHOUT_EXTENSION", withExtension: "pdf") else { return }
let documentInteractionController = UIDocumentInteractionController.init(url: path)
documentInteractionController.delegate = self
documentInteractionController.presentPreview(animated: true)
}
}
}
extension ViewController: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
}
When I use UIDocumentInteractionController on iphoneX with iOS 11.1.2, it doesn't show, and log on the console as:
[ShareSheet] ERROR: <_UIDICActivityViewController: 0x107075c00> timed out waiting to establish a connection to the ShareUI view service extension.
but the same code runs ok on other device with iOS 11.1.2, only the iphoneX cannot preview the documentInteraction menu. Is there some thing wrong with my code? Please tell me if you find the solution.
My code:
var doc: UIDocumentInteractionController?
fileprivate func openWithOtherApp(with url: URL) -> Void {
let doc = UIDocumentInteractionController(url: url)
self.doc = doc
doc.delegate = self
DispatchQueue.main.async { [weak self] in
guard let weakSelf = self else {
return
}
if weakSelf.doc?.presentOpenInMenu(from: weakSelf.view.bounds, in: weakSelf.view, animated: true) != true {
Toast(message: "No app on you iPhone can open this file.")
}
}
}
delegate:
extension UniversalPreviewController: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
func documentInteractionControllerRectForPreview(_ controller: UIDocumentInteractionController) -> CGRect {
return self.view.bounds
}
func documentInteractionControllerViewForPreview(_ controller: UIDocumentInteractionController) -> UIView? {
return self.view
}
func documentInteractionControllerWillBeginPreview(_ controller: UIDocumentInteractionController) {
NSLog("documentInteractionControllerWillBeginPreview")
}
func documentInteractionControllerDidEndPreview(_ controller: UIDocumentInteractionController) {
self.doc = nil
}
}
My Latest Test:
When i use permanent file url(drag the file into the project filelist), It works.
let permanentUrl = Bundle.main.url(forResource: "INBOX24-2.2-resolv", withExtension: "conf")
self.doc = UIDocumentInteractionController.init(url: permanentUrl!)
//permanentUrl:file:///var/containers/Bundle/Application/40F14112-208E-4A97-8214-D55CC8E597C4/xxx.app/INBOX24-2.2-resolv.conf
But when i change to the real code that files storage on the apps documents directory it doesn't work,code like this:
let url = URL.init(fileURLWithPath: storedPath
self.doc = UIDocumentInteractionController.init(url: url)
//url:file:///var/mobile/Containers/Data/Application/9624FFB3-8CB0-437C-A67B-C71AAB314370/Documents/INBOX24-2.2-resolv.conf
I found the same issue in my iPhone, I just restarted my iPhone and it was resolved.
I have a UITabView in UIViewController, all tab items are linked to other UIViewControllers. I have written a swift code of downloading a file through internet. when I select second tabItem, this code runs well, it downloads and previews the downloaded file, Then when I click on first tabItem and then again click on second tabItem; file downloads well but it doesn't show any preview instead xCode gives me a warning message:
What I want is download file and preview file both should work when I again click on the second tabItem. whatever the code is.
warning: attempt to present QLPreviewController on KPIViewController whose view is not in the window hierarchy
I have found many solutions on the internet but it didn't work
first solution says to use
let viewer = UIDocumentInteractionController(URL: NSURL(fileURLWithPath: path))
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(viewer, animated: true, completion: nil)
but this function
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(viewer, animated: true, completion: nil)
do not accept
UIDocumentInteractionController
second solution says to override the existing presentViewController function to
override func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
let APP_DELEGATE = UIApplication.sharedApplication().delegate
var presentedModalVC:UIViewController = (APP_DELEGATE!.window?!.rootViewController?.presentedViewController)!
if presentedModalVC == true {
while((presentedModalVC.presentedViewController) != nil){
presentedModalVC = presentedModalVC.presentedViewController!
}
presentedModalVC.presentViewController(viewControllerToPresent, animated: flag, completion: nil)
}
else{
APP_DELEGATE?.window!!.rootViewController?.presentViewController(viewControllerToPresent, animated: flag, completion: nil)
}
}
I tried this but it also needs a UIViewController in its parameters where I have UIDocumentInteractionController
I know these function cannot accept UIDocumentInteractionController type viewController.
here is my whole swift code:
// KPIViewController.swift
// download
//
// Created by me on 15/03/2016.
// Copyright © 2016 me. All rights reserved.
//
import UIKit
class KPIViewController: UIViewController,UITabBarDelegate, NSURLSessionDownloadDelegate, UIDocumentInteractionControllerDelegate{
#IBOutlet weak var tabBar1: UITabBar!
#IBOutlet weak var login_Item: UITabBarItem!
#IBOutlet weak var QAreport_Item: UITabBarItem!
#IBOutlet weak var KpiWebView: UIWebView!
#IBOutlet weak var progressView: UIProgressView!
var downloadTask: NSURLSessionDownloadTask!
var backgroundSession: NSURLSession!
var downloadReport:Bool!
var AuditCodeOfDashboardCell:String?
var AuditCodeForPDF:String?
let isDirectory: ObjCBool = false
override func viewDidLoad() {
super.viewDidLoad()
self.progressView.hidden = true
downloadReport = false
// Do any additional setup after loading the view.
self.tabBar1.delegate = self
}
override func viewDidAppear(animated: Bool) {
self.progressView.hidden = true
downloadReport = false
let backgroundSessionConfiguration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("backgroundSession")
backgroundSession = NSURLSession(configuration: backgroundSessionConfiguration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
progressView.setProgress(0.0, animated: false)
var requestURL = NSURL!()
var request = NSURLRequest!()
// loading data from web
if AuditCodeOfDashboardCell != nil{
print(self.AuditCodeOfDashboardCell)
requestURL = NSURL(string:“my URL string&\(AuditCodeOfDashboardCell)”)
request = NSURLRequest(URL: requestURL!)
AuditCodeForPDF = AuditCodeOfDashboardCell
AuditCodeOfDashboardCell = nil
}else{
requestURL = NSURL(string:“my URL string”)
request = NSURLRequest(URL: requestURL!)
}
KpiWebView.loadRequest(request)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {
print("selected tabItem: \(item.tag)")
switch (item.tag) {
case 1:
let loginVC = self.storyboard!.instantiateViewControllerWithIdentifier("loginViewController") as! LoginView
presentViewController(loginVC, animated: true, completion: nil)
break
case 2:
if AuditCodeForPDF != nil{
downloadReport = true
let url = NSURL(string: “my URL string&\(AuditCodeForPDF)”)!
urlToDownload = url
}
// if let resultController = storyboard!.instantiateViewControllerWithIdentifier(“2”) as? QAReportViewController {
// presentViewController(resultController, animated: true, completion: nil)
// }
break
default:
break
}
if downloadReport == true{
let url = NSURL(string: “my URL string&\(AuditCodeForPDF)”)!
downloadTask = backgroundSession.downloadTaskWithURL(url)
self.progressView.hidden = false
downloadTask.resume()
downloadReport = false
}
}
// - - Handling download file- - - - - - - - -
func URLSession(session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didFinishDownloadingToURL location: NSURL){
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let documentDirectoryPath:String = path.first!
let fileManager = NSFileManager()
var destinationURLForFile = NSURL(fileURLWithPath: documentDirectoryPath.stringByAppendingString("/Report.pdf"))
if fileManager.fileExistsAtPath(destinationURLForFile.path!){
// showFileWithPath(destinationURLForFile.path!)
do{
try fileManager.removeItemAtPath(destinationURLForFile.path!)
destinationURLForFile = NSURL(fileURLWithPath: documentDirectoryPath.stringByAppendingString("/Report.pdf"))
}catch{
print(error)
}
}
do {
try fileManager.moveItemAtURL(location, toURL: destinationURLForFile)
// show file
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.showFileWithPath(destinationURLForFile.path!)
})
}catch{
print("An error occurred while moving file to destination url")
}
}
func showFileWithPath(path: String){
let isFileFound:Bool? = NSFileManager.defaultManager().fileExistsAtPath(path)
if isFileFound == true{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let viewer = UIDocumentInteractionController(URL: NSURL(fileURLWithPath: path))
viewer.delegate = self
viewer.presentPreviewAnimated(true)
})
}
}
func URLSession(session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64){
progressView.setProgress(Float(totalBytesWritten)/Float(totalBytesExpectedToWrite), animated: true)
}
func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController) -> UIViewController{
return self
}
func documentInteractionControllerDidEndPreview(controller: UIDocumentInteractionController) {
print("document preview ends")
}
}
I cannot find any proper solution that solve my problem. I am new with swift
please anyone on help me. Thanks in advance
UIDocumentInteractionController is not kind of UIViewController. So you cannot present an UIDocumentInteractionController with presentViewController: method.
Checkout https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDocumentInteractionController_class/
You can presenting a document preview or options menus with UIDocumentInteractionController.