在物理设备上进行测试,同时尝试重命名某个文件夹中的文件时,就会出现问题
在我的 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())
}
}
}
任何指示和帮助将不胜感激。
this document,用户首先需要选择一个文件夹来为应用程序提供访问权限,方法是返回该文件夹及其所有内容的安全范围 URL(然后您可以将其添加为书签以供以后使用)。用户选择文件夹后,他们可以在另一个选择器上对指定的文件类型执行任何操作。
对于用户来说,理解为什么他们必须这样做仍然太复杂,所以我制作了一个用户友好的流程,以便用户了解如何继续。它仍然缺少一些成功的祝酒通知,但你会明白的。代码位于我之前发布的repo上。
如果有一种方法可以一步完成此操作,而不必提示用户选择文件夹,那就太好了。请告诉我。在那之前,这就是我能想到的。