无法修改 iOS 应用程序沙箱之外的文件。权限问题

问题描述 投票:0回答:1
我正在开发一个 iOS 应用程序(最新的 Swift、iOS 和 Xcode 版本 atm 编写),需要重命名位于任何地方(本地存储、外部存储、云等)的音频文件(并稍后修改其元数据)。

在物理设备上进行测试,同时尝试重命名某个文件夹中的文件时,就会出现问题

在我的 iPhone 上,应用程序在模拟器上进行测试时会按预期进行。在我的实体 iPhone 上,我收到一条错误记录,指出我无权执行此操作。

“filename.mp3”无法移动,因为您无权访问“randomFolder”。

我进行了谷歌搜索并询问了 GPT,了解了

FileCoordinator

UIDocumentPickerViewController
u2060、
startAccessingSecurityScopedResource
。我也看了
这个视频,最后跟着这个另一个

在我的设备上运行时,我看到

url.startAccessingSecurityScopedResource

 抛出:

无法获取对安全范围资源的访问权限。

我也尝试过像这样访问父目录:

let parentURL = url.deletingLastPathComponent()


然后用

parentURL

代替,但也失败了。


我注意到我的应用程序未显示在

设置 -> 隐私和安全 -> 文件和文件夹列表中。在用户同意的情况下,我的应用程序没有触发在此处添加任何对话框。

摆弄

Info.plist

 中的权利和功能也没有帮助,但我不太确定应该添加哪些。

所以:

我在 iPhone 上的文件和文件夹隐私设置中看到的有关权限和应用程序未出现的问题是否是因为我的应用程序是使用不在 Apple 开发者计划中的开发者帐户进行签名的?这是我最后的想法,因为我不明白我还应该尝试什么。


这是一个最小的可重现示例,也可以在

here找到:

// ContentView.swift import SwiftUI struct ContentView: View { @State private var isFilePickerPresented: Bool = false @State private var newName: String = "" @EnvironmentObject var bookmarkController: BookmarkController var body: some View { VStack { TextField("Enter new name", text: $newName) .padding() .border(Color.black, width: 1) .background(.gray.opacity(0.2)) Button("Show document picker") { isFilePickerPresented.toggle() } .padding() .frame(maxWidth: .infinity) .background(.blue) .foregroundStyle(.white) .sheet(isPresented: $isFilePickerPresented, content: { DocumentPicker(newName: $newName) }) } .padding() } }
// DocumentPicker.swift

import SwiftUI
import UIKit
import UniformTypeIdentifiers
import MobileCoreServices


/// Rename selected file from browser
func renameFile(at fileURL: URL, to newName: String) throws {
    let fileExtension = fileURL.pathExtension
    let directory = fileURL.deletingLastPathComponent()

    // Create a new URL with the updated name and the original extension
    let renamedURL = directory.appendingPathComponent(newName).appendingPathExtension(fileExtension)

    try FileManager.default.moveItem(at: fileURL, to: renamedURL)
}

/// ATM we use UTType.audio but could add specific audio formats later, if needed.
let supportedTypes: [UTType] = [UTType.audio]

struct BrowserView: View {
    
    @State private var fileBrowserIsShown = false
    @Binding var newName: String
    
    var body: some View {
        DocumentPicker(newName: $newName)
            .environmentObject(BookmarkController())
    }
}

struct DocumentPicker: UIViewControllerRepresentable {
    @Binding var newName: String
    @EnvironmentObject private var bookmarkController: BookmarkController
    
    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
        let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes)
        documentPicker.delegate = context.coordinator
        return documentPicker
    }
    
    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {
        print("updateUIViewController documentPicker")
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self, newName)
    }

    class Coordinator: NSObject, UIDocumentPickerDelegate {
        var parent: DocumentPicker
        var newName: String
        
        init(_ parent: DocumentPicker, _ newName: String = "") {
            self.parent = parent
            self.newName = newName
        }
        
        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
            // save bookmark
            print("documentPicker \(urls[0])")
            parent.bookmarkController.addBookmark(for: urls[0])
            
            // Rename the file
            var error: NSError?
            
            NSFileCoordinator().coordinate(readingItemAt: urls[0], options: [], error: &error) { coordinatedURL in
                do {
                    //                let data = try Data(contentsOf: newURL)
                    print("urls[0]: \(urls[0])")
                    print("coordinatedURL: \(coordinatedURL)")
                    print("renamedURL: \(newName)")
                    try renameFile(at: coordinatedURL, to: newName)
                } catch  {
                    print("Error: \(error.localizedDescription)")
                }
            }
        }
    }
}

// BookmarkController.swift

import SwiftUI
import MobileCoreServices

class BookmarkController: ObservableObject {
    @Published var urls: [URL] = []
    
    init() {
        loadAllBookmarks()
    }
    
    func addBookmark(for url: URL) {
        let parentURL = url.deletingLastPathComponent()
        print("adding bookmark for \(parentURL)")
        do {
            guard parentURL.startAccessingSecurityScopedResource() else {
                print("Failed to obtain access to the security-scoped resource.")
                return
            }
            
            defer { parentURL.stopAccessingSecurityScopedResource() }
            
            let bookmarkData = try parentURL.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil)
            
            let uuid = UUID().uuidString
            try bookmarkData.write(to: getAppSandboxDir().appendingPathComponent(uuid))
            
            urls.append(parentURL)
        } catch {
            print("Error Adding Bookmark: \(error.localizedDescription)")
        }
    }
    
    func loadAllBookmarks() {
        // Get all the bookmark files
        let files = try? FileManager.default.contentsOfDirectory(at: getAppSandboxDir(), includingPropertiesForKeys: nil)
        // Map over the bookmark files
        self.urls = files?.compactMap { file in
            do {
                let bookmarkData = try Data(contentsOf: file)
                var isStale = false
                // Get the URL from each bookmark
                let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale)
                
                guard !isStale else {
                    // Handle stale bookmarks
                    return nil
                }
                print("loaded bookmark: \(url)")
                // Return URL
                return url
            } catch {
                print("Error Loading Bookmark: \(error.localizedDescription)")
                return nil
            }
        } ?? []
    }
    
    private func getAppSandboxDir() -> URL {
        // TODO see 0 index
        FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    }
}

// App.swift

import SwiftUI

@main
struct renamerApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(BookmarkController())
        }
    }
}

任何指示和帮助将不胜感激。

ios swift iphone permissions
1个回答
0
投票
我终于找到了解决办法。根据

this document,用户首先需要选择一个文件夹来为应用程序提供访问权限,方法是返回该文件夹及其所有内容的安全范围 URL(然后您可以将其添加为书签以供以后使用)。用户选择文件夹后,他们可以在另一个选择器上对指定的文件类型执行任何操作。

对于用户来说,理解为什么他们必须这样做仍然太复杂,所以我制作了一个用户友好的流程,以便用户了解如何继续。它仍然缺少一些成功的祝酒通知,但你会明白的。代码位于我之前发布的

repo上。

如果有一种方法可以一步完成此操作,而不必提示用户选择文件夹,那就太好了。请告诉我。在那之前,这就是我能想到的。

© www.soinside.com 2019 - 2024. All rights reserved.