-1

The user can add a image, which name would be stored in the database under "imagename". the database would be synchronized in a second on iPhone and example iPad of the user. A image get a new Name and be saved.

Here is the function for this:

private func saveImage() -> String? {
    guard let image = inputImage,
          let imageData = image.jpegData(compressionQuality: 0.5) else {
        return nil
    }
    
    let timestamp = Date().timeIntervalSince1970
    let imageName = "\(UUID().uuidString)-\(timestamp)"
    
    if let existingImageName = tool?.imagename {
        // Delete the existing image
        if let iCloudDocumentsURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents") {
            let existingImagePath = iCloudDocumentsURL.appendingPathComponent(existingImageName)
            try? FileManager.default.removeItem(at: existingImagePath)
        } else {
            let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let existingImagePath = documentsDirectory.appendingPathComponent(existingImageName)
            try? FileManager.default.removeItem(at: existingImagePath)
        }
    }
    
    var imagePath: URL
    if let iCloudDocumentsURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents") {
        // iCloud storage is available
        imagePath = iCloudDocumentsURL.appendingPathComponent(imageName)
    } else {
        // iCloud storage is not available, use local storage
        let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        imagePath = documentsDirectory.appendingPathComponent(imageName)
    }
    
    do {
        try imageData.write(to: imagePath)
        print("Image saved with name: \(imageName)")
        return imageName
    } catch {
        print("Error saving image: \(error.localizedDescription)")
        return nil
    }
}

When I like to view the picture then I use the loadImage() Function:

func loadImage() {
    if let inputImage = inputImage {
        // An image has already been selected, so just display it
        self.inputImage = inputImage
    } else if let imageName = tool?.imagename {
        // Check both local and iCloud Documents directories for the image
        let localImagePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent(imageName)

        var iCloudImagePath: URL?
        if let iCloudURL = FileManager.default.url(forUbiquityContainerIdentifier: nil) {
            iCloudImagePath = iCloudURL.appendingPathComponent("Documents").appendingPathComponent(imageName)
        }

        if FileManager.default.fileExists(atPath: localImagePath.path) {
            // Image exists in local directory, load it from there
            do {
                let imageData = try Data(contentsOf: localImagePath)
                inputImage = UIImage(data: imageData)
            } catch {
                print("Error loading image from local directory: \(error.localizedDescription)")
                inputImage = nil
            }
        } else if let iCloudImagePath = iCloudImagePath, FileManager.default.fileExists(atPath: iCloudImagePath.path) {
            // Image exists in iCloud Documents directory, load it from there
            do {
                let imageData = try Data(contentsOf: iCloudImagePath)
                inputImage = UIImage(data: imageData)
            } catch {
                print("Error loading image from iCloud Documents directory: \(error.localizedDescription)")
                inputImage = nil
            }
        } else {
            // Image not found in either directory
            inputImage = nil
        }
    } else {
        // No image selected
        inputImage = nil
    }
}

This is the persistanceController:

struct PersistenceController {
    
    static let shared = PersistenceController()

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        do {

            try viewContext.save()

        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    let container: NSPersistentCloudKitContainer

    
    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "HereIsTheContainerName")
        
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
    }
}

I can save the Image and on the local System it would viewed. When I watch the value of imagename at the iPad, then the correct name is there, but the image would not be viewed. It seems like that the image would not be synchronized to the iClouddrive.

Both devices have the option enabled for the app "iCloud" and "iCloudDrive".

Can you find a fail of my code?

  • Please update your question with more debugging details. What happens exactly in the `saveImage` method on the iPhone? What happens exactly in the `loadImage` method on the iPad? Is the file saved in the iCloud or Documents folder? How large is the image? If it is going to iCloud, it might take some time to transfer. – HangarRash Apr 11 '23 at 05:35
  • Unrelated but why are you adding a timestamp to the UUID? There's no point. A UUID is already unique. Adding the timestamp just adds needless complication. – HangarRash Apr 11 '23 at 05:36

1 Answers1

0

My assumption why the images are only visible locally is, that within the saveImage() function the call url(forUbiquityContainerIdentifier:) returns nil (according to the doc: "if the container could not be located or if iCloud storage is unavailable for the current user or device"), hence with the else statement the image gets saved in your local storage (not within the iCloud container).

Note: the function url(forUbiquityContainerIdentifier:) does not depend on network connectivity, meaning you can save the image in the container and let iOS synchronize them.

Design hint: the function should not be called directly from the main thread (use something like DispatchQueue.global to retrieve the url).

Refere to doc

func url(forUbiquityContainerIdentifier:)

Important

Do not call this method from your app’s main thread. Because this method might take a nontrivial amount of time to set up iCloud and return the requested URL, you should always call it from a secondary thread. To determine if iCloud is available, especially at launch time, check the value of the ubiquityIdentityToken property instead.

PS: if you want to force the download have a look at startDownloadingUbiquitousItem(at:) and to delete the local copy only evictUbiquitousItem(at:)

cuda12
  • 600
  • 3
  • 15
  • The first paragraph of this answer is incorrect. While `url(forUbiquityContainerIdentifier:)` may take a non-trivial amount of time to return, it does return and the code flow is not affected. Your conclusion that the image only gets saved within the local storage because of this simply isn't valid. – HangarRash Apr 12 '23 at 15:03
  • Yeah I agree with your comment - I rephrased the answer. – cuda12 Apr 12 '23 at 15:42
  • OK. So now your answer is just a (good) guess. This is why I left comments below the question asking the OP to clarify what is actually happening. Until (if) the OP provides more details, the only thing anyone can do is guess what might be happening. – HangarRash Apr 12 '23 at 15:48
  • Its an estimated guess to pin-point the issue, plus IMHO there is no need (except if there is an additional use-case) to store the image locally - with a separate fileManager call. Thats the second part of the answer above. – cuda12 Apr 13 '23 at 08:19
  • Sadly the OP seems to have abandoned the question. I don't understand why someone would use up a large portion of their reputation on a bounty and then never come back to respond to comments or answers. – HangarRash Apr 13 '23 at 14:55
  • yeah kinda not the point of the whole story! have a good one – cuda12 Apr 15 '23 at 07:49
  • @Miracle Johnson nice move setting a bounty, letting it expire but then excepting the answer! – cuda12 Apr 26 '23 at 21:36