Learn Courses My Dashboard

Disable Save to Files

Does anyone know how to disable the ability to “Save to Files?” I see that someone here, claims it can be done by following the instructions here. I attempted to follow the instructions, but they are not working…

I want to prevent users from saving a PDF to the phone locally. However, I want to allow them only the ability to print the PDF.

Here’s my custom UIActivityItemProvider class per the instructions instructions here (updated for Swift 5):

class ActivityItemProvider: UIActivityItemProvider {
    override func activityViewController(
        _ activityViewController: UIActivityViewController,
        itemForActivityType activityType: UIActivity.ActivityType?
    ) -> Any? {
        // Check that the ActivityType is not nil
        if activityType != nil {
            // in here we'll check activityType = "com.apple.CloudDocsUI.AddToiCloudDrive" (Save to Files),
            // activityType = "com.apple.mobileslideshow.StreamShareService" (Shared Album)
            if activityType!.rawValue.contains("com.apple.CloudDocsUI.AddToiCloudDrive") || activityType!.rawValue.contains("com.apple.mobileslideshow.StreamShareService") {
                // dismiss activityViewController first
                activityViewController.dismiss(animated: true, completion: nil)
                // show alert controller, we can using UIApplication.shared.keyWindow?.rootViewController to present alert
                return nil
            }
            return self.placeholderItem
        }
        // Else if nil, then return nil
        else {
            return nil
        }
    }
}

Here is the rest of the struct, which creates the UIKit Share view via the UIActivityViewController. Supposedly by using the above custom class it would have prevented the user from seeing the Save to Files option, but that’s not working:

import SwiftUI

struct ShareSheet: UIViewControllerRepresentable {
    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil
    
    // Used to dismiss the shared sheet
    @Environment(\.presentationMode) var presentationMode
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<ShareSheet>) -> UIActivityViewController {
        
        // Custom ActivityItemProvider code from the
        let item = ActivityItemProvider.init(placeholderItem: activityItems[0])
        
        // Create the UIActivityViewController, which displays shared information
//        let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
        let controller = UIActivityViewController.init(activityItems: [item], applicationActivities: applicationActivities)
        
        // MARK: - Exclude Activities Besides Print
        controller.excludedActivityTypes = [
            .addToReadingList,
            .assignToContact,
            .saveToCameraRoll,
            .copyToPasteboard,
            .airDrop,
            .postToFacebook,
            .postToTwitter,
            .message,
            .markupAsPDF,
            .mail,
            .openInIBooks,
            .postToFlickr,
            .postToTencentWeibo,
            .postToVimeo,
            .postToWeibo
        ]
        
        // Used to dismiss the share screen
        controller.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
            self.presentationMode.wrappedValue.dismiss()
        }
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ShareSheet>) {
        
    }
}

Right now, it still displays the “Save to Files” option:

Here’s my SwiftUI code that generates the PDF and calls the UIKit UIActivityViewController that shows the Share options:

import SwiftUI

struct ContentView: View {
    // Holds the generated PDF data
    @State var pdfData: Data = Data()
    
    // Tracks whether/ not the sheet to share is popped-up
    @State var isSharePresented: Bool = false
    
    var body: some View {
        
        VStack {
            Button {
                generatePdf()
            } label: {
                Text("Generate PDF")
            }
            
            // MARK: - Show or Hide PDF
            // Only display this button, if the PDF is not empty
            if pdfData.isEmpty == false {
                
                // MARK: - Share and Print PDF
                Button("Print PDF") {
                    // Set the State property to true, so the View gets displayed
                    self.isSharePresented = true
                }
                // The State variable isSharePresented triggers the view to pop-up the share options as a sheet from below
                .sheet(isPresented: $isSharePresented, onDismiss: {
                    isSharePresented = false
                }, content: {
                    // Display the UIKitRepresentable view
                    ShareSheet(activityItems: [self.pdfData])
                })
            }
        }
    }
    
    /**
     Method generates a PDF and assigns the resulting data to the @State pdfData property
     
     It also saves the PDF to the document directory by the user.
     */
    func generatePdf() {
        let pageRect = CGRect(x: 0, y: 0, width: 612, height: 792)
        
        let renderer = UIGraphicsPDFRenderer(bounds: pageRect)
        
        let title = "All’s Well That Ends Well - The COUNT’s palace.\n"
        let text = """
COUNTESS

In delivering my son from me, I bury a second husband.

BERTRAM

And I in going, madam, weep o’er my father’s death
anew: but I must attend his majesty’s command, to
whom I am now in ward, evermore in subjection.

LAFEU

You shall find of the king a husband, madam; you,
sir, a father: he that so generally is at all times
good must of necessity hold his virtue to you; whose
worthiness would stir it up where it wanted rather
than lack it where there is such abundance.

COUNTESS

What hope is there of his majesty’s amendment?

LAFEU

He hath abandoned his physicians, madam; under whose
practises he hath persecuted time with hope, and
finds no other advantage in the process but only the
losing of hope by time.

COUNTESS

This young gentlewoman had a father,–O, that
‘had’! how sad a passage ’tis!–whose skill was
almost as great as his honesty; had it stretched so
far, would have made nature immortal, and death
should have play for lack of work. Would, for the
king’s sake, he were living! I think it would be
the death of the king’s disease.
"""
        
        let titleAttributes = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 36)]
        let textAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12)]
        
        let formattedTitle = NSMutableAttributedString(string: title, attributes: titleAttributes)
        let formattedText = NSAttributedString(string: text, attributes: textAttributes)
        formattedTitle.append(formattedText)
        
        let data = renderer.pdfData { ctx in
            ctx.beginPage()
            
            formattedTitle.draw(in: pageRect.insetBy(dx: 50, dy: 50))
        }
        
        DispatchQueue.main.async {
            // Set the pdfData @State variable of this view equal to this pdf data
            self.pdfData = data
        }
    }
    
}

Honestly, I wouldn’t bother. If the user has the ability to print the document, they can save it as a PDF and bypass your restriction against doing so.

Maybe it’s not something everyone knows, but you can pinch out on the preview in the print sheet to get a PDF document that you can then use the share button on and AFAIK you have no control over what goes in that one since it’s system-generated.

So while you may be able to prevent them from using your app’s share sheet to save a file, they can get around that restriction pretty easily.

Hi Patrick, I’d still like to stop it, if possible… If my app handles Social Security numbers in an office, I’m okay with them printing that material in the office, but they should not be allowed to save it to the phone. For security reasons, it’s better if they cannot store it on the phone.

It seems like the screen captures can be prevented: How to Prevent Screen Capture at iOS 14 | Better Programming

Of course, they would be able to bypass the protections later. Still, it would be nice to make them go to extra trouble to do these kinds of things, hence my desire to prevent the Save to Files. It seems that many others want this feature too per the activity on the StackOverflows referenced above.