You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

适配App Store:macOS沙箱环境下URL内容访问权限问题求解

macOS沙箱环境下SwiftUI读取文件夹权限问题解决

问题场景

使用SwiftUI的fileImporter选择文件夹后,尝试遍历其内容时在macOS触发权限错误,代码在iOS上可正常运行,但因应用需上架App Store无法关闭沙箱,报错信息如下:

count: 1
- - - - - - - - - - -
/Volumes/mac Drive/My Releases
Item is a folder! Name: My Releases
The file “My Releases” couldn’t be opened because you don’t have permission to view it.
- - - - - - - - - - -

问题原因

macOS沙箱机制下,通过文件选择器获取的URL默认不具备临时访问权限,必须主动调用startAccessingSecurityScopedResource()申请权限,访问完成后需调用stopAccessingSecurityScopedResource()释放权限,否则会被沙箱拦截操作。

解决方法

修改代码,确保在访问文件选择器返回的URL及其子内容时,先启动安全范围资源访问,所有文件操作都在授权有效期内执行。

修改后的完整代码

import SwiftUI

struct ContentView: View {
    
    @State var isShowing: Bool = false
    
    var body: some View {
        VStack {
            Button("Import Folder") {
                isShowing.toggle()
            }
        }
        .padding()
        .fileImporter(isPresented: $isShowing, allowedContentTypes: [.folder], allowsMultipleSelection: false, onCompletion: { results in
            switch results {
            case .success(let items):
                print("count: \(items.count)")
                print("- - - - - - - - - - -")
                
                for item in items {
                    print(item.path)
                    // 申请临时访问权限,失败则跳过
                    guard item.startAccessingSecurityScopedResource() else {
                        print("无法获取文件夹访问权限")
                        continue
                    }
                    // 确保函数退出时自动释放权限
                    defer {
                        item.stopAccessingSecurityScopedResource()
                    }
                    lookToURL(item: item)
                }
                
                print("- - - - - - - - - - -")
                
            case .failure(let error):
                print(error)
            }
        })
    }
}

func lookToURL(item: URL) {
    isItemDirectory(at: item, result: { itemResult in
        guard let itemIsDirectory = itemResult else {
            print("无法判断项目类型")
            return
        }
        
        if itemIsDirectory {
            print("Item is a folder! Name:", item.lastPathComponent)
            contentsOfDirectory(url: item, contents: { contents in
                for content in contents {
                    lookToURL(item: content)
                }
            })
        } else {
            print("Item is a file! Name:", item.lastPathComponent)
        }
    })
}

func contentsOfDirectory(url: URL, contents: ([URL]) -> Void) {
    do {
        let contentsOfDirectory = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: [.isDirectoryKey])
        contents(contentsOfDirectory)
    } catch let error {
        print(error.localizedDescription)
    }
}

func isItemDirectory(at url: URL, result: (Bool?) -> Void) {
    do {
        let values = try url.resourceValues(forKeys: [.isDirectoryKey])
        result(values.isDirectory)
    } catch let error {
        print(error.localizedDescription)
        result(nil)
    }
}

func isItemDirectory(at url: URL) -> Bool? {
    do {
        let values = try url.resourceValues(forKeys: [.isDirectoryKey])
        return values.isDirectory
    } catch let error {
        print(error.localizedDescription)
        return nil
    }
}

关键修改点

  1. 在fileImporter成功回调中,对选中的文件夹URL调用startAccessingSecurityScopedResource()申请临时权限,并用defer确保权限自动释放
  2. 简化contentsOfDirectory函数,移除冗余的文件存在性判断,直接通过异常处理捕获错误
  3. 优化可选值绑定逻辑,让代码更简洁易读

内容的提问来源于stack exchange,提问作者swiftPunk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 00:22:03