Папка приложения SwiftUI iCloud Drive не отображается в приложении «Файлы»IOS

Программируем под IOS
Ответить
Anonymous
 Папка приложения SwiftUI iCloud Drive не отображается в приложении «Файлы»

Сообщение Anonymous »

Я создаю приложение SwiftUI, которое интегрируется с iCloud Drive. Цель состоит в том, чтобы папка «Документы» приложения отображалась на iCloud Drive пользователя, как и другие приложения, открывающие свои папки.
У меня есть следующая настройка:
  • Разрешения включают iCloud с включенными документами
  • Использование FileManager.default.url(forUbiquityContainerIdentifier: nil) для получения контейнера
  • Убедитесь, что каталог Documents существует внутри контейнера.
  • Успешная запись тестовых файлов с помощью copyItem.
Что я пробовал:
  • Удаление приложения и установка новой.
  • Увеличение сборки и версии и новая установка.
  • Использование TeamID (но это не требуется при использовании автоматически создаваемого iCloud...)
  • Это тестовое приложение позволяет отображать все сохраненные файлы и создавать новые в документах.
Тестовое приложение печатает:
✅ Documents directory ensured: file:///private/var/mobile/Library/Mobile%20Documents/iCloud~/Documents/
✅ iCloud container found: file:///private/var/mobile/Library/Mobile%20Documents/iCloud~/
✅ Container write access confirmed
✅ Saved test file to iCloud: file:///private/var/mobile/Library/Mobile%20Documents/iCloud~/Documents/Hello iCloud ...
[ERROR] couldn't fetch remote operation IDs: NSError: Cocoa 257 "The file couldn’t be opened because you don’t have permission to view it."
"Error returned from daemon: Error Domain=com.apple.accounts Code=7 "(null)"
✅ iCloud upload complete: file:///private/var/mobile/Library/Mobile%20Documents/iCloud~/Documents/Hello iCloud ...

На моем iPhone файл загружается нормально, и я вижу его загруженным на своем Mac. Но в приложении «Файлы» (iOS и macOS) папка приложения не отображается в iCloud Drive.
Вопросы:
  • Нужно ли сделать шаг, чтобы открыть папку «Документы» приложения в iCloud Drive?
  • Эти ошибки указывают на неверную конфигурацию (Cocoa 257 / код учетной записи 7) или они безвредны?
  • Ожидается ли это при запуске на устройстве с поддержкой разработчика?
Я действительно в тупике, и каждый результат поиска здесь или в Google не решил проблему и не подтолкнул меня в правильном направлении.
Что действительно раздражает, так это то, что я знаю, что все это синхронизируется. Если я спамлю кнопку «Загрузить тестовый файл» на своем iPhone, а затем перейду в «Настройки» > «iCloud» > «Управление хранилищем» > «Приложение» на своем Mac — размер увеличится. И если я удалю из iCloud (на Mac), список приложений iOS очистится. Итак, они разговаривают, а хранилище работает.

import SwiftUI
import UniformTypeIdentifiers

@main
struct testApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

struct ContentView: View {
@State private var cloudService: CloudService = .init()
@State private var documents: [CloudDocument] = []
@State private var isRefreshing = false

var body: some View {
NavigationView {
VStack {
Button("Upload Test File") {
uploadTestFile()
}
.padding()

Button("Refresh Documents") {
refreshDocuments()
}
.padding()

if isRefreshing {
ProgressView("Loading documents...")
.padding()
}

List(documents) { doc in
VStack(alignment: .leading) {
Text(doc.name)
.font(.headline)
Text("Status: \(doc.status)")
.font(.caption)
.foregroundColor(doc.isDownloaded ? .green : .orange)
if let size = doc.size {
Text("Size: \(ByteCountFormatter.string(fromByteCount: size, countStyle: .file))")
.font(.caption2)
}
}
}
}
.navigationTitle("iCloud Documents")
.onAppear {
// Give iCloud a moment to initialize
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
refreshDocuments()
}
}
}
}

private func uploadTestFile() {
guard cloudService.isCloudAvailable else {
print("❌ iCloud not available")
return
}

// Create a temporary test file
let temp = URL.temporaryDirectory.appendingPathComponent("Hello iCloud \(Date().timeIntervalSince1970).txt")
let message = "Hello! Time: \(Date())"

do {
try message.write(to: temp, atomically: true, encoding: .utf8)

// Copy to iCloud
let target = try cloudService.copyToCloud(from: temp)
print("✅ Saved test file to iCloud:", target)

// Start download to ensure it's available
try cloudService.startDownloadIfNeeded(for: target)

// Monitor upload status
cloudService.monitorUpload(of: target) { uploaded, error in
if let error = error {
print("⚠️ Upload error:", error)
} else if uploaded {
print("✅ iCloud upload complete:", target)
// Refresh our document list
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
refreshDocuments()
}
}
}

// Cleanup temp file
try? FileManager.default.removeItem(at: temp)

} catch {
print("❌ iCloud save failed:", error)
}
}

private func refreshDocuments() {
isRefreshing = true

Task {
do {
let docs = try await cloudService.fetchDocuments()
await MainActor.run {
self.documents = docs
self.isRefreshing = false
}
} catch {
print("❌ Failed to fetch documents:", error)
await MainActor.run {
self.isRefreshing = false
}
}
}
}
}

struct CloudDocument: Identifiable {
let id = UUID()
let name: String
let url: URL
let isDownloaded: Bool
let size: Int64?
let status: String
}

@Observable
final class CloudService {
static let shared = CloudService()

private(set) var containerURL: URL?
private(set) var documentsDirectory: URL?
private var metadataQuery: NSMetadataQuery?

init() {
refreshContainer()
observeIdentityChanges()
}

var isCloudAvailable: Bool {
documentsDirectory != nil
}

// MARK: - Container Setup
private func refreshContainer() {
// Add a small delay to ensure iCloud is ready
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// Try with nil first (uses default container)
var containerURL: URL?

// First try the explicit container ID
containerURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)

// If that fails, try with nil (uses the first/default container)
if containerURL == nil {
containerURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)
print("⚠️ Explicit container failed, trying default container")
}

if let url = containerURL {
self.containerURL = url
self.documentsDirectory = url.appendingPathComponent("Documents", isDirectory: true)
self.ensureDocumentsDirectory()
print("✅ iCloud container found:", url)

// Check if we can actually write to it
self.testContainerAccess()
} else {
self.containerURL = nil
self.documentsDirectory = nil
print("❌ No iCloud container available - check:")
print(" • iCloud Drive enabled in Settings")
print(" • App ID configured in App Store Connect")
print(" • Entitlements match container ID")
print(" • User signed into iCloud")
}
}
}

private func testContainerAccess() {
guard let docsDir = documentsDirectory else { return }

let testFile = docsDir.appendingPathComponent(".test_access")

do {
try "test".write(to: testFile, atomically: true, encoding: .utf8)
try FileManager.default.removeItem(at: testFile)
print("✅ Container write access confirmed")
} catch {
print("❌ Container write test failed:", error)
print(" This indicates a permissions or configuration issue")
}
}

private func ensureDocumentsDirectory() {
guard let dir = documentsDirectory else { return }

do {
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
print("✅ Documents directory ensured:", dir)
} catch {
print("❌ Failed to create documents directory:", error)
}
}

private func observeIdentityChanges() {
NotificationCenter.default.addObserver(
forName: .NSUbiquityIdentityDidChange,
object: nil,
queue: .main
) { [weak self] _ in
print("📱 iCloud identity changed - refreshing container")
self?.refreshContainer()
}
}

// MARK: - File Operations
func copyToCloud(from localURL: URL) throws -> URL {
guard let docs = documentsDirectory else {
throw CocoaError(.fileNoSuchFile, userInfo: [
NSLocalizedDescriptionKey: "iCloud Drive is not available"
])
}

var target = docs.appendingPathComponent(localURL.lastPathComponent)

// Remove existing file if it exists
if FileManager.default.fileExists(atPath: target.path) {
try FileManager.default.removeItem(at: target)
}

try FileManager.default.copyItem(at: localURL, to: target)

// Set the file to be stored in iCloud
var resourceValues = URLResourceValues()
resourceValues.hasHiddenExtension = false
try target.setResourceValues(resourceValues)

return target
}

// MARK: - Document Discovery
func fetchDocuments() async throws -> [CloudDocument] {
guard let docsDir = documentsDirectory else {
throw CocoaError(.fileNoSuchFile, userInfo: [
NSLocalizedDescriptionKey: "iCloud Drive is not available"
])
}

return try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global(qos: .userInitiated).async {
do {
// First, try to get directory contents
let contents = try FileManager.default.contentsOfDirectory(
at: docsDir,
includingPropertiesForKeys: [
.ubiquitousItemDownloadingStatusKey,
.fileSizeKey,
.ubiquitousItemHasUnresolvedConflictsKey
]
)

var documents: [CloudDocument] = []

for url in contents {
// Skip hidden files and directories
if url.lastPathComponent.hasPrefix(".") { continue }

var isDownloaded = false
var size: Int64? = nil
var status = "Unknown"

// Get resource values
let resourceValues = try? url.resourceValues(forKeys: [
.ubiquitousItemDownloadingStatusKey,
.fileSizeKey,
.ubiquitousItemHasUnresolvedConflictsKey
])

if let values = resourceValues {
size = values.fileSize.map { Int64($0) }

if let downloadStatus = values.ubiquitousItemDownloadingStatus {
switch downloadStatus {
case .current:
status = "Downloaded"
isDownloaded = true
case .downloaded:
status = "Downloaded"
isDownloaded = true
case .notDownloaded:
status = "In Cloud"
isDownloaded = false
default:
status = "Downloading"
isDownloaded = false
}
}

if values.ubiquitousItemHasUnresolvedConflicts == true {
status += " (Conflict)"
}
}

let doc = CloudDocument(
name: url.lastPathComponent,
url: url,
isDownloaded: isDownloaded,
size: size,
status: status
)
documents.append(doc)
}

continuation.resume(returning: documents)

} catch {
print("❌ Error fetching documents:", error)
continuation.resume(throwing: error)
}
}
}
}

// MARK: - Download Management
func startDownloadIfNeeded(for url: URL) throws {
var isDownloaded = false

if let resourceValues = try? url.resourceValues(forKeys: [.ubiquitousItemDownloadingStatusKey]) {
if let downloadStatus = resourceValues.ubiquitousItemDownloadingStatus {
switch downloadStatus {
case .current, .downloaded:
isDownloaded = true
default:
isDownloaded = false
}
}
}

if !isDownloaded {
do {
try FileManager.default.startDownloadingUbiquitousItem(at: url)
print("📥 Started downloading:", url.lastPathComponent)
} catch {
print("❌ Failed to start download:", error)
throw error
}
}
}

// MARK: - Upload Monitoring
func monitorUpload(of url: URL, handler: @escaping (Bool, Error?) -> Void) {
metadataQuery?.stop()
metadataQuery = NSMetadataQuery()
guard let query = metadataQuery else { return }

query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
query.predicate = NSPredicate(format: "%K == %@", NSMetadataItemFSNameKey, url.lastPathComponent)

NotificationCenter.default.addObserver(
forName: .NSMetadataQueryDidUpdate,
object: query,
queue: .main
) { [weak self] _ in
self?.handleMetadataUpdate(query: query, handler: handler)
}

query.start()
}

private func handleMetadataUpdate(query: NSMetadataQuery, handler: @escaping (Bool, Error?) -> Void) {
query.disableUpdates()
defer { query.enableUpdates() }

for item in query.results.compactMap({ $0 as? NSMetadataItem }) {
let isUploaded = item.value(forAttribute: NSMetadataUbiquitousItemIsUploadedKey) as? Bool ?? false
let error = item.value(forAttribute: NSMetadataUbiquitousItemUploadingErrorKey) as? Error

if isUploaded || error != nil {
handler(isUploaded, error)
// Stop monitoring after completion or error
query.stop()
}
}
}
}


Подробнее здесь: https://stackoverflow.com/questions/797 ... -files-app
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»