9 Replies
      Latest reply on May 8, 2019 6:10 AM by nkeets
      nkeets Level 1 Level 1 (0 points)

        I'm trying to use QLPreviewPanel with a file stored in Caches, but my app crashes when sandbox is enabled. I've tried different directories instead of Caches but they don't seem to make any difference.

         

        Here is a bit more background about what I'm doing: The app is receiving assets in a packed form from the Internet. The assets can be images, text files, documents, etc. After unpacking, each asset is contained in a Data. What I would like to do is present a QuickLook view of this asset to the user. So I don't even need to use the filesystem, I could do it directly from memory if QuickLook supported that.

         

        I originally posted this question in a reply to: https://forums.developer.apple.com/message/255625. I'm reposting as a new question in the hope of getting more visibility. Any advice is appreciated.

        • Re: Use of QLPreviewPanel in a sandboxed app
          Claude31 Level 8 Level 8 (5,735 points)

          Would be better to postactual code and detail of the crash (whee, which message...)

            • Re: Use of QLPreviewPanel in a sandboxed app
              nkeets Level 1 Level 1 (0 points)

              Sure, just paste this code to a new project and connect the IBAction to a button.

               

              class ViewController: NSViewController, QLPreviewPanelDataSource {
                  var previewPanel: QLPreviewPanel!
                  var url: URL!
              
                  override func viewDidAppear() {
                      let path = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]
                      self.url = URL(fileURLWithPath: path).appendingPathComponent("test.html")
                      let data = "<html><body><h1>Hello</h1></body></html>".data(using: .utf8)!
                      try! data.write(to: url)
                      NSApp.mainWindow!.nextResponder = self
                  }
              
                  @IBAction func toggleQuickLook(_ send: NSButton) {
                      if QLPreviewPanel.sharedPreviewPanelExists() && QLPreviewPanel.shared()!.isVisible {
                          QLPreviewPanel.shared()!.orderOut(nil)
                      } else {
                          QLPreviewPanel.shared()!.makeKeyAndOrderFront(nil)
                      }
                  }
              
                  override func acceptsPreviewPanelControl(_ panel: QLPreviewPanel!) -> Bool {
                      return true
                  }
              
                  override func beginPreviewPanelControl(_ panel: QLPreviewPanel!) {
                      panel.dataSource = self
                      self.previewPanel = panel
                  }
              
                  override func endPreviewPanelControl(_ panel: QLPreviewPanel!) {
                      self.previewPanel = nil
                  }
              
                  func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
                      return 1
                  }
              
                  func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
                      return self.url as QLPreviewItem
                  }
              }
              
              

               

              The error is: Thread 1: EXC_BAD_ACCESS (code=1, address=0x20)

                • Re: Use of QLPreviewPanel in a sandboxed app
                  janabanana Level 1 Level 1 (0 points)

                  Disclaimer: I am not a Swifty and it may be that what I am about to say is irrelevant.

                   

                  In this code:

                   

                  let path = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]

                  self.url = URL(fileURLWithPath: path).appendingPathComponent("test.html")

                   

                  If it was objective C, I see a problem and a crash.  The NSSearchPathForDirectoriesInDomains returns an array of strings and then you would need to get one of the NSString objects out of the array.  Like I said, I am not a swifty so maybe this code is pulling a string object out of the array.

                  • Re: Use of QLPreviewPanel in a sandboxed app
                    QuinceyMorris Level 8 Level 8 (5,950 points)

                    >>my app crashes when sandbox is enabled

                     

                    So, if it's not crashing without the sandbox, then the probability is that you're trying to access a directory that requires explicit user approval to access.

                     

                    >>I've tried different directories instead of Caches but they don't seem to make any difference

                     

                    Did you try the Application Support directory? That's one that can accessed while sandboxed. (Note: When using Application Support unsandboxed, the convention is to create your own subfolder named with your app's bundle ID, so you don't clobber other app's files. When sandboxed, you don't need to create the subfolder — though of course you can if you want.)

                     

                    The problem you have is that line 06 of your code fragment isn't safe, and NSSearchPathForDirectoriesInDomains returns no useful error information if it fails. (You should regard this function as outdated.) Instead, use FileManager.url(for:in:appropriateFor:create:) instead. It throws an error if something goes wrong.

                     

                    If you do use a function that returns an array (FileManager.urls(for:in:) is the modern equivalent of NSSearchPathForDirectoriesInDomains), don't use the array subscript without first check that the array has something in it. I suspect this is the actual cause of your crash right now.

                     

                    Or, you may actually want to use a temp directory for this. Instructions for creating a temp directory in a modern way are here:

                     

                    https://developer.apple.com/documentation/foundation/filemanager/1407693-url

                     

                    under the "Discussion" heading. (In this case, the 3rd parameter is used only to determine which disk volume to use for the temp directory. IIRC you can pass nil if you don't care.)

                     

                    In general, your code shows a lot of places where you are "ignoring" optionality (using the "!" operator to prevent messages from the compiler counts as ignoring). Your code will be a lot more robust if you check for unexpected nil results at the point where they're generated, and don't propagate values with optional types through your code. The "guard let" and "if let" constructs are your best friend here.

                      • Re: Use of QLPreviewPanel in a sandboxed app
                        nkeets Level 1 Level 1 (0 points)

                        Changing lines 3-4 to the following doesn't make any difference:

                         

                        self.url = try! FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("test.html")

                         

                        In any case the file is created succesfully, so I definitely have the right access permissions. I'm assuming that QuickLook is running under different permissions that is causing the app to crash. So is there a location that my app can write to and QuickLook can read when running sandboxed? What should this line be in order for it to work?

                         

                        PS: I'm well aware of the dangers of force unwrapping/force trying, this is a reduced example...

                  • Re: Use of QLPreviewPanel in a sandboxed app
                    john daniel Level 3 Level 3 (380 points)

                    Thanks for the clarification. Can I ask for a bit more clarification?

                     

                    Are you trying to write an app that has a QuickLook generator? Or are you just trying to use the QLPreviewPanel referenced in that other thread?

                     

                    Here is my concern. That other thread was from 2017. I had never heard of those classes before. There is no guaraneed that they ever worked in the sandbox or that they will in the future.

                     

                    Why do you want the QuickLook view anyway? How is that better than just displaying the data in a window? Quicklook has changed since 2017. The QLPreviewPanel may not work anymore. Have you tried the QLPreviewView instead? You might not get some of the toolbar features in a modern QuickLook window, but you could easly reproduce that in your own window.

                    • Re: Use of QLPreviewPanel in a sandboxed app
                      nkeets Level 1 Level 1 (0 points)

                      Turns out this works if I enable com.apple.security.network.client entitlement. My app doesn't do any outgoing network connections, are there any workarounds to use QuickLook without this entitlement?