Use NSItemProvider in combination with NSFilePromiseProvider - swift

How can I use a NSFilePromiseProvider in combination with .onDrag in SwiftUI? It only allows me to return an NSItemProvider, but for this, the file must already exist on the computer as the callback of registerFileRepresentation is called eagerly.
I want to drag a remote file displayed in my application to my computer desktop. With AppKit I used file promises for this use case, but for SwiftUI I found no solution so far.
struct ContentView: View {
var body: some View {
Text("Draggable Text")
.onDrag {
let provider = NSItemProvider()
provider.registerFileRepresentation(
forTypeIdentifier: UTType.fileURL.identifier,
fileOptions: [],
visibility: .all
) {
// This file must already exists. How to provide a file promise here?
let fileUrl = URL(fileURLWithPath: "file:///path/to/file.txt")
$0(fileUrl, true, nil)
return nil
}
return provider
}
}
}

Here's a limited workaround. It uses a FileHandle to write to the created file even after the drop has finished.
It reliably works for drop targets that move the file (e.g. Finder).
It fails for drop targets that read from the file (e.g. Apple Mail) if the file hasn't been completely written when the drop event occurs. In this situation, the empty placeholder content is read by the target app.
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Drag me")
.onDrag {
let provider = NSItemProvider()
provider.registerDataRepresentation(for: .fileURL, visibility: .all, loadHandler: loadFileForItemProvider)
return provider
}
}
#Sendable
func loadFileForItemProvider(onComplete callback: #escaping (Data?, Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: 100)
Task.detached {
do {
let temporaryDirectory = try FileManager.default.url(for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: .documentsDirectory, create: true)
let url = temporaryDirectory.appendingPathComponent("some_file.pdf")
try Data().write(to: url) // create temporary, emptyfile
let handle = try FileHandle(forWritingTo: url)
callback(url.dataRepresentation, nil)
let fileData = try await ... // load/generate file data here
try handle.write(contentsOf: fileData)
try? handle.close()
progress.completedUnitCount = 100
} catch {
callback(nil, error)
}
}
return progress
}
}
Remember to clean up the temporary directory after a certain duration or when quittung/launching the app.

Related

implementation of NSMetadataQuery along with UIDocuments in swiftUI

I am trying to make a document based app in swiftUI with a custom UI. I want iCloud capabilities in my app. I am trying to use iCloud Document (No cloudKit) way for storing data on iCloud container. I am using UIDocument and it's working. It's storing data to iCloud and I am able to retrieve it back.
Now the thing is when I run the app on two devices (iphone and iPad) and make changes to a file on one device, the changes are not reflecting on the other device while the file or say app is open. I have to close the app and relaunch it to see the changes.
I know I have to implement NSMetadataQuery to achieve this but I am struggling with it. I don't know any objective-C. I have been searching on the internet for a good article but could not find any. Can you please tell how do I implement this feature in my app. I have attach the working code of UIDocument and my Model class.
Thank you in advance !
UIDocument
class NoteDocument: UIDocument {
var notes = [Note]()
override func load(fromContents contents: Any, ofType typeName: String?) throws {
if let contents = contents as? Data {
if let arr = try? PropertyListDecoder().decode([Note].self, from: contents) {
self.notes = arr
return
}
}
//if we get here, there was some kind of problem
throw NSError(domain: "NoDataDomain", code: -1, userInfo: nil)
}
override func contents(forType typeName: String) throws -> Any {
if let data = try? PropertyListEncoder().encode(self.notes) {
return data
}
//if we get here, there was some kind of problem
throw NSError(domain: "NoDataDomain", code: -2, userInfo: nil)
}
}
Model
class Model: ObservableObject {
var document: NoteDocument?
var documentURL: URL?
init() {
let fm = FileManager.default
let driveURL = fm.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
documentURL = driveURL?.appendingPathComponent("savefile.txt")
document = NoteDocument(fileURL: documentURL!)
}
func loadData(viewModel: ViewModel) {
let fm = FileManager.default
if fm.fileExists(atPath: (documentURL?.path)!) {
document?.open(completionHandler: { (success: Bool) -> Void in
if success {
viewModel.notes = self.document?.notes ?? [Note]()
print("File load successfull")
} else {
print("File load failed")
}
})
} else {
document?.save(to: documentURL!, for: .forCreating, completionHandler: { (success: Bool) -> Void in
if success {
print("File create successfull")
} else {
print("File create failed")
}
})
}
}
func saveData(_ notes: [Note]) {
document!.notes = notes
document?.save(to: documentURL!, for: .forOverwriting, completionHandler: { (success: Bool) -> Void in
if success {
print("File save successfull")
} else {
print("File save failed")
}
})
}
func autoSave(_ notes: [Note]) {
document!.notes = notes
document?.updateChangeCount(.done)
}
}
Note
class Note: Identifiable, Codable {
var id = UUID()
var title = ""
var text = ""
}
This is a complex topic. Apple do provide some sample swift code, the Document-Based App Programming Guide for iOS and iCloud Design Guide.
There is also some good third party guidance: Mastering the iCloud Document Store.
I would recommend reading the above, and then return to the NSMetaDataQuery API. NSMetaDataQuery has an initial gathering phase and a live-update phase. The later phase can remain in operation for the lifetime of your app, allowing you to be notified of new documents in your app's iCloud container.

How can I use firebase storage to download images in a file and show them in a table view?

Good afternoon,
I have been stuck on this problem for months. I am trying to use firebase storage to save image files that a user uploaded. The program should then be able to update the queue and show the image in a horizontal table view. Kinda like netflix where its titles of movies/shows but mine would just be pictures. After trying to figure this out, this is what I came up with. Here is to receive the images
class ImageRecieve : ObservableObject {
#Published var songImageArrayURL = [URL]()
#Published var data : Data?
#Published var songImage : NSImage?
#Published var AlbumCoverArray = [NSImage]()
func GetURLS(){
//we want to get the download urls
bfRef.listAll { (result, error) in
if let error = error{ //if theres an error, print it
print(error.localizedDescription)
}
let prefixes = result.prefixes
//loop to search each song prefix
for i in prefixes.indices{
//get the song of each prefix
prefixes[i].listAll { (result, error) in
if let error = error {
print(error.localizedDescription)
}
else {
let items = result.items
//if anything contains ".mp3" dont add it to array.
for j in items.indices{
if(!items[j].name.contains("mp3")){
SongImage.append(items[j])
self.download(SongImage: items[j])
}
}
}
}
}
}
}
func download(SongImage:StorageReference){
//get download url
DispatchQueue.main.async {
SongImage.downloadURL { (url, error) in
if let error = error { //if there is an error print it
print(error.localizedDescription)
}
else {
if(url != nil){
self.songImage = NSImage(byReferencing: url!)
self.AlbumCoverArray.append(self.songImage!)
}
}
}
}
}
func load(){
if(self.songImageArrayURL.isEmpty){
GetURLS()
}
print(self.songImageArrayURL)
for i in self.songImageArrayURL.indices{
print(self.songImageArrayURL[i])
DispatchQueue.global().async{
if let data = try? Data(contentsOf: self.songImageArrayURL[i]){
if let image = NSImage(data:data){
DispatchQueue.main.async {
self.songImage = image
}
}
}
}
}
}
func cancel(){
}
}
here is to load the images :
struct LoadImages<Placeholder: View>: View {
#ObservedObject var loader : ImageRecieve
private var placeholder : Placeholder?
init(placeholder: Placeholder? = nil) {
loader = ImageRecieve()
self.placeholder = placeholder
}
var body: some View {
image
.onAppear(perform: loader.GetURLS)
.onDisappear(perform: loader.cancel)
}
private var image: some View{
ForEach(loader.AlbumCoverArray.indices,id:\.self){
i in
Group{
if(self.loader.songImage != nil){
Image(nsImage:self.loader.AlbumCoverArray[i]).resizable().frame(width:50, height:50)
}
else{
self.placeholder
}
}
}
}
}
the problem I've been stuck on is that the photos are only downloading one at a time and not listing one by one. For example, they show one image and then switch to the next. I would like an array of images. So that the images get added to the list. I've tried using an image array but it doesnt work.
photos are only downloading one at a time and not listing one by one.
in all languages an array/list is processed sequentially, you might want to use multi-Threading for parallelism. use a queue and assign few threads which download image, after each download pop the element from queue.
all the child threads append/push the data to the main thread. in that manner you will be able to display images as they load.
PS:i am != swiftie but seeing your programming i sense turmoil. try improving your code grammar and avoid too many functions and spaces.

Give a new url to the browser with Swift macOS

I could find a way to open a specific browser (with macOS and Swift):
#IBAction func frx(_ sender: NSButton) {
NSWorkspace.shared.open(URL(fileURLWithPath: "/Applications/Firefox.app"))
}
Is it possible to give to that Firefox window a new url in a posterior moment and reload the page? (Give the address not when I launch the application but later)
struct Firefox {
static func open(path: String) {
let ff_url = NSURL(fileURLWithPath: "/Applications/Firefox.app", isDirectory: true) as URL
if let www_url = URL(string: path) {
NSWorkspace.shared.open([www_url], withApplicationAt: ff_url, configuration: NSWorkspace.OpenConfiguration()) { app, error in
if let error = error {
// handle error
}
if let _ = app {
// handle success
}
}
} else {
// handle error
}
}
}

How to properly use query generation tokens?

I'm trying to get an example project using CoreData and QueryGenerationTokens working. The essence of the project is to be committing changes to a background context on a timer (emulating changes coming down from a server) that shouldn't be displayed until an action is taken on the UI (say, a button press).
Currently, I have changes being saved on the background context (an entity is being added every 5s and saved) and they are automatically coming into the view context (as expected, .automaticallyMergesChangesFromParent is set to true). Where things go wrong, I am pinning the view context before any of these changes happen to the current query generation token. I would expect the view to not update with the background items being added, but it is updating with them. So it seems the query generation tokens are having no effect?
Some of the possible issues I've thought of:
the only example I've found from Apple doesn't show them using it with a fetched results controller (I'm using #FetchRequest in SwiftUI, which I'm almost entirely certain is essentially the same), so that may have an effect?
.automaticallyMergeChangesFromParent shouldn't be used and I should try a merge policy, but that doesn't seem to work either and conceptually, it seems the query generation tokens should work with this and pin to the generation no matter the merging.
Code for view - handles loading data from view context
// Environment object before fetch request necessary
// Passed in wherever main view is instantiated through .environment()
#Environment(\.managedObjectContext) var managedObjectContext
// Acts as fetched results controller, loading data automatically into items upon the managedObjectContext updating
// ExampleCoreDataEntity.retrieveItemsFetchRequest() is an extension method on the entity to easily get a fetch request for the type with sorting
#FetchRequest(fetchRequest: ExampleCoreDataEntity.retrieveItemsFetchRequest()) var items: FetchedResults<ExampleCoreDataEntity>
var body: some View {
NavigationView {
// Button to refresh and bring in changes
Button(
action: {
do {
try self.managedObjectContext.setQueryGenerationFrom(.current)
self.managedObjectContext.refreshAllObjects()
} catch {
print(error.localizedDescription)
}
},
label: { Image(systemName: "arrow.clockwise") }
)
// Creates a table of items sorted by the entity itself (entities conform to Hashable)
List(self.items, id: \.self) { item in
Text(item.name ?? "")
}
}
}
Code in SceneDelegate (where a SwiftUI application starts up) where I also initialize what is needed for CoreData:
// Setup and pass in environment of managed object context to main view
// via extension on persistent container that sets up CoreData stack
let managedObjectContext = NSPersistentContainer.shared.viewContext
do {
try managedObjectContext.setQueryGenerationFrom(.current)
} catch {
print(error.localizedDescription)
}
let view = MainView().environment(\.managedObjectContext, managedObjectContext)
// Setup background adding
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(backgroundCode), userInfo: nil, repeats: true)
// Setup window and pass in main view
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: view)
Function adding data in the background:
#objc func backgroundCode() {
ExampleCoreDataEntity.create(names: ["background object"], in: backgroundContext, shouldSave: true)
}
Setup of NSPersistentContainer:
extension NSPersistentContainer {
private struct SharedContainerStorage {
static let container: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Core_Data_Exploration")
container.loadPersistentStores { (description, error) in
guard error == nil else {
assertionFailure("CoreData: Unresolved error \(error!.localizedDescription)")
return
}
container.viewContext.automaticallyMergesChangesFromParent = true
}
return container
}()
}
static var shared: NSPersistentContainer {
return SharedContainerStorage.container
}
}
Create/Read/Update/Delete functions on the entity:
extension ExampleCoreDataEntity {
static func retrieveItemsFetchRequest() -> NSFetchRequest<ExampleCoreDataEntity> {
let request: NSFetchRequest<ExampleCoreDataEntity> = ExampleCoreDataEntity.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \ExampleCoreDataEntity.creationDate, ascending: false)]
return request
}
static func create(names: [String], in context: NSManagedObjectContext, shouldSave save: Bool = false) {
context.perform {
names.forEach { name in
let item = ExampleCoreDataEntity(context: context)
item.name = name
item.creationDate = Date()
item.identifier = UUID()
}
do {
if save {
try context.save()
}
} catch {
// print error
}
}
}
func delete(in context: NSManagedObjectContext, shouldSave save: Bool = false) {
context.perform {
let name = self.name ?? "an item"
context.delete(context.object(with: self.objectID))
do {
if save {
try context.save()
}
} catch {
// print error
}
}
}
}
The issue was container.viewContext.automaticallyMergesChangesFromParent = true
That property cannot be set to true while working with query generation tokens. I came back to this issue and found this in the header of NSManagedObjectContext documented above automaticallyMergesChangesFromParent:
Setting this property to YES when the context is pinned to a non-current query generation is not supported.
The general flow of getting it to work is the following:
setting the query generation token to .current
calling .refreshAllObjects() on the view context
calling .performFetch() on the fetched results controller
This last part goes against the code I put in the original question which used #FetchRequest - currently, I can't figure out a way that doesn't seem extremely hacky to make it manually refetch. To get around this, I made an intermediate store class containing a FetchedResultsController that adopts its delegate protocol. That store also adopts ObservableObject which allows a SwiftUI view to listen to its changes when calling objectWillChange.send() within the ObservableObject adopting store.
In the documentation you linked to in the question you will see it says:
"Calling save(), reset(), mergeChangesFromContextDidSaveNotification:, or mergeChangesFromRemoteContextSave(:intoContexts:) on any pinned context will automatically advance it to the most recent version for the operation and then reset its query generation to currentQueryGenerationToken."
The reason you are seeing the changes from the background save is automaticallyMergesChangesFromParent is just convenience for mergeChangesFromContextDidSaveNotification so your generation is advancing.
FYI here is another sample project uses query generations - Synchronizing a Local Store to the Cloud
And here is the relevant code:
/*
See LICENSE folder for this sample’s licensing information.
Abstract:
A class to set up the Core Data stack, observe Core Data notifications, process persistent history, and deduplicate tags.
*/
import Foundation
import CoreData
// MARK: - Core Data Stack
/**
Core Data stack setup including history processing.
*/
class CoreDataStack {
/**
A persistent container that can load cloud-backed and non-cloud stores.
*/
lazy var persistentContainer: NSPersistentContainer = {
// Create a container that can load CloudKit-backed stores
let container = NSPersistentCloudKitContainer(name: "CoreDataCloudKitDemo")
// Enable history tracking and remote notifications
guard let description = container.persistentStoreDescriptions.first else {
fatalError("###\(#function): Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores(completionHandler: { (_, error) in
guard let error = error as NSError? else { return }
fatalError("###\(#function): Failed to load persistent stores:\(error)")
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.transactionAuthor = appTransactionAuthorName
// Pin the viewContext to the current generation token and set it to keep itself up to date with local changes.
container.viewContext.automaticallyMergesChangesFromParent = true
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
}
// Observe Core Data remote change notifications.
NotificationCenter.default.addObserver(
self, selector: #selector(type(of: self).storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange, object: container)
return container
}()
/**
Track the last history token processed for a store, and write its value to file.
The historyQueue reads the token when executing operations, and updates it after processing is complete.
*/
private var lastHistoryToken: NSPersistentHistoryToken? = nil {
didSet {
guard let token = lastHistoryToken,
let data = try? NSKeyedArchiver.archivedData( withRootObject: token, requiringSecureCoding: true) else { return }
do {
try data.write(to: tokenFile)
} catch {
print("###\(#function): Failed to write token data. Error = \(error)")
}
}
}
/**
The file URL for persisting the persistent history token.
*/
private lazy var tokenFile: URL = {
let url = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("CoreDataCloudKitDemo", isDirectory: true)
if !FileManager.default.fileExists(atPath: url.path) {
do {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
} catch {
print("###\(#function): Failed to create persistent container URL. Error = \(error)")
}
}
return url.appendingPathComponent("token.data", isDirectory: false)
}()
/**
An operation queue for handling history processing tasks: watching changes, deduplicating tags, and triggering UI updates if needed.
*/
private lazy var historyQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return queue
}()
/**
The URL of the thumbnail folder.
*/
static var attachmentFolder: URL = {
var url = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("CoreDataCloudKitDemo", isDirectory: true)
url = url.appendingPathComponent("attachments", isDirectory: true)
// Create it if it doesn’t exist.
if !FileManager.default.fileExists(atPath: url.path) {
do {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
} catch {
print("###\(#function): Failed to create thumbnail folder URL: \(error)")
}
}
return url
}()
init() {
// Load the last token from the token file.
if let tokenData = try? Data(contentsOf: tokenFile) {
do {
lastHistoryToken = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSPersistentHistoryToken.self, from: tokenData)
} catch {
print("###\(#function): Failed to unarchive NSPersistentHistoryToken. Error = \(error)")
}
}
}
}
// MARK: - Notifications
extension CoreDataStack {
/**
Handle remote store change notifications (.NSPersistentStoreRemoteChange).
*/
#objc
func storeRemoteChange(_ notification: Notification) {
print("###\(#function): Merging changes from the other persistent store coordinator.")
// Process persistent history to merge changes from other coordinators.
historyQueue.addOperation {
self.processPersistentHistory()
}
}
}
/**
Custom notifications in this sample.
*/
extension Notification.Name {
static let didFindRelevantTransactions = Notification.Name("didFindRelevantTransactions")
}
// MARK: - Persistent history processing
extension CoreDataStack {
/**
Process persistent history, posting any relevant transactions to the current view.
*/
func processPersistentHistory() {
let taskContext = persistentContainer.newBackgroundContext()
taskContext.performAndWait {
// Fetch history received from outside the app since the last token
let historyFetchRequest = NSPersistentHistoryTransaction.fetchRequest!
historyFetchRequest.predicate = NSPredicate(format: "author != %#", appTransactionAuthorName)
let request = NSPersistentHistoryChangeRequest.fetchHistory(after: lastHistoryToken)
request.fetchRequest = historyFetchRequest
let result = (try? taskContext.execute(request)) as? NSPersistentHistoryResult
guard let transactions = result?.result as? [NSPersistentHistoryTransaction],
!transactions.isEmpty
else { return }
// Post transactions relevant to the current view.
DispatchQueue.main.async {
NotificationCenter.default.post(name: .didFindRelevantTransactions, object: self, userInfo: ["transactions": transactions])
}
// Deduplicate the new tags.
var newTagObjectIDs = [NSManagedObjectID]()
let tagEntityName = Tag.entity().name
for transaction in transactions where transaction.changes != nil {
for change in transaction.changes!
where change.changedObjectID.entity.name == tagEntityName && change.changeType == .insert {
newTagObjectIDs.append(change.changedObjectID)
}
}
if !newTagObjectIDs.isEmpty {
deduplicateAndWait(tagObjectIDs: newTagObjectIDs)
}
// Update the history token using the last transaction.
lastHistoryToken = transactions.last!.token
}
}
}
// MARK: - Deduplicate tags
extension CoreDataStack {
/**
Deduplicate tags with the same name by processing the persistent history, one tag at a time, on the historyQueue.
All peers should eventually reach the same result with no coordination or communication.
*/
private func deduplicateAndWait(tagObjectIDs: [NSManagedObjectID]) {
// Make any store changes on a background context
let taskContext = persistentContainer.backgroundContext()
// Use performAndWait because each step relies on the sequence. Since historyQueue runs in the background, waiting won’t block the main queue.
taskContext.performAndWait {
tagObjectIDs.forEach { tagObjectID in
self.deduplicate(tagObjectID: tagObjectID, performingContext: taskContext)
}
// Save the background context to trigger a notification and merge the result into the viewContext.
taskContext.save(with: .deduplicate)
}
}
/**
Deduplicate a single tag.
*/
private func deduplicate(tagObjectID: NSManagedObjectID, performingContext: NSManagedObjectContext) {
guard let tag = performingContext.object(with: tagObjectID) as? Tag,
let tagName = tag.name else {
fatalError("###\(#function): Failed to retrieve a valid tag with ID: \(tagObjectID)")
}
// Fetch all tags with the same name, sorted by uuid
let fetchRequest: NSFetchRequest<Tag> = Tag.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: Schema.Tag.uuid.rawValue, ascending: true)]
fetchRequest.predicate = NSPredicate(format: "\(Schema.Tag.name.rawValue) == %#", tagName)
// Return if there are no duplicates.
guard var duplicatedTags = try? performingContext.fetch(fetchRequest), duplicatedTags.count > 1 else {
return
}
print("###\(#function): Deduplicating tag with name: \(tagName), count: \(duplicatedTags.count)")
// Pick the first tag as the winner.
let winner = duplicatedTags.first!
duplicatedTags.removeFirst()
remove(duplicatedTags: duplicatedTags, winner: winner, performingContext: performingContext)
}
/**
Remove duplicate tags from their respective posts, replacing them with the winner.
*/
private func remove(duplicatedTags: [Tag], winner: Tag, performingContext: NSManagedObjectContext) {
duplicatedTags.forEach { tag in
defer { performingContext.delete(tag) }
guard let posts = tag.posts else { return }
for case let post as Post in posts {
if let mutableTags: NSMutableSet = post.tags?.mutableCopy() as? NSMutableSet {
if mutableTags.contains(tag) {
mutableTags.remove(tag)
mutableTags.add(winner)
}
}
}
}
}
}

iCloud Drive Issue: "[DocumentManager] Failed to associate thumbnails for picked URL"

I've created a JSON string file from an object containing multiple properties. This is the object:
RecipeFile : Codable {
var name: String
var theRecipeIngredients: [String]
var theRecipeSteps: [String]
var theRecipeRating: Int
var theRecipeCategory: String
var theRecipeIndexStrings: String
var theRecipeImage: String?
I create the JSON string file with this code:
let json_encoder = JSONEncoder()
let recipeFileName = recipeToDisplay.name! + UUID().uuidString + ".json"
let exportFilePath = getDocumentsDirectory().appendingPathComponent(recipeFileName)
do {
let jsonData = try json_encoder.encode(exportRecipeFile)
if let jsonString = String(data: jsonData, encoding: .utf8)
{
try jsonString.write(to: exportFilePath, atomically: false, encoding: .utf8)
}
} catch {
print(error.localizedDescription)
}
I upload it to iCloud Drive. I import the string file from iCloud Drive using UIDocumentPickerViewController. I can parse the imported file just fine. However, I get this message (edited to remove some path info) in the xCode debug area when func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) is called:
[DocumentManager] Failed to associate thumbnails for picked URL
file:///....Bourbon%20Chocolate%20Walnut%20Pie18D20181-DAFD-499C-9873-7D3E0794C37A.json
with the Inbox copy
file:///....Bourbon%20Chocolate%20Walnut%20Pie18D20181-DAFD-499C-9873-7D3E0794C37A.json:
Error Domain=QLThumbnail Code=2 "(null)"
UserInfo={NSUnderlyingError=0x149a042b0 {Error
Domain=GSLibraryErrorDomain Code=3 "Generation not found"
UserInfo={NSDescription=Generation not found}}}
Any idea what is causing this to be generated?
The didPickDocumentsAt code starts as follows:
let data = try? Data(contentsOf: urls[0]) as Data
let json_decoder = JSONDecoder()
do {
let importRecipeFile = try json_decoder.decode(RecipeFile.self, from: data!)
let importedRecipeToSave = Recipe(context: theMOC)
importedRecipeToSave.name = importRecipeFile.name
importedRecipeToSave.category = importRecipeFile.theRecipeCategory
importedRecipeToSave.rating = Int16(importRecipeFile.theRecipeRating)
importedRecipeToSave.terms = importRecipeFile.theRecipeIndexStrings
importedRecipeToSave.addedToGroceryList = false
You can safely ignore this message. When you import a file from iCloud, iOS tries to copy the thumbnail from iCloud to the imported copy, but for JSON files there's no thumbnail to copy and it logs this. This is not an error on your side.
I can reproduce this issue with SwiftUI with iOS 14. After Originally I present the UIDocumentPickerViewController after a ToolbarItem pressed and it works fine. Later I refactor the UI and the View is pushed from other parent View and this error occurs and the JSON is not received.
The same thing has happened to me, and I have not found a concrete solution in any forum. But, by testing and mixing code that I found in the forums finally worked for me. Still, I don't know exactly what's wrong.
I leave my code here in case it's useful for someone, although it's been many months since this question.
func importarCsv(sender: UIBarButtonItem) {
let types = [kUTTypePDF,kUTTypeUTF8PlainText]
let importMenu = UIDocumentPickerViewController(documentTypes: types as [String], in: .import)
if #available(iOS 11.0, *) {
importMenu.allowsMultipleSelection = false
}
importMenu.delegate = self
present(importMenu, animated: true, completion: nil)
}
extension MaterialViewController: UIDocumentPickerDelegate {
internal func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt urls: URL) {
print("urls : \(urls)")
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
print("close")
controller.dismiss(animated: true, completion: nil)
}
}
I had this problem when i presented the UIDocumentPicker from another UIViewController before adding it as a child view controller on its parent view controller.