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

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.

Related

Use NSItemProvider in combination with NSFilePromiseProvider

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.

Swift Document Picker, can not reach the file

I am writing a project which involves picking a file and getting the content of the file. However, I think it is not reaching the correct url of the file.
Here is the function where it calls the document picker. It is activated by a button.
#IBAction func selectFile(_ sender: Any) {
let documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypePlainText as String], in: .import)
documentPicker.delegate = self
documentPicker.allowsMultipleSelection = false
present(documentPicker, animated: true, completion: nil)
}
And here is the extension to UIDocumentPickerViewController
extension ViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print(urls)
do {
let fileContent = try String(contentsOf: urls[0], encoding: .utf8)
print(fileContent)
} catch {
return
}
}
}
In the console output, fileContent is not being printed out, instead, here is what it prints out.
Failed to associate thumbnails for picked URL
file:///Users/<user>/Library/Developer/CoreSimulator/Devices/480A2D02-810F-435E-BF44-4B2F6FC614A9/data/Containers/Data/Application/BAA825D0-C4D8-4B33-AFB3-3737ADCA0B29/Documents/test2.txt with the Inbox copy
file:///Users/<user>/Library/Developer/CoreSimulator/Devices/480A2D02-810F-435E-BF44-4B2F6FC614A9/data/Containers/Data/Application/BAA825D0-C4D8-4B33-AFB3-3737ADCA0B29/tmp/<project>-Inbox/test2.txt:
Error Domain=QLThumbnailErrorDomain Code=102 "(null)"
UserInfo={NSUnderlyingError=0x600003348060
{Error Domain=GSLibraryErrorDomain Code=3 "Generation not found" UserInfo={NSDescription=Generation not found}}}
There aren't many resources about this online, can someone help look at what I did wrong here?
Actually I made a small change on the UIDocumentPickerViewController initialiser. Adding the updated code below.
#IBAction func selectFile(_ sender: Any) {
let documentPicker = UIDocumentPickerViewController(documentTypes: ["public.text"], in: .import)
documentPicker.delegate = self
documentPicker.allowsMultipleSelection = false
present(documentPicker, animated: true, completion: nil) }
Rest of the code same, only replaced this ["public.text"] instead of [kUTTypePlainText as String].
May be it resolves your problem. Please let me know the feedback.
Credit gose to adrian check this link using-uidocumentpickerviewcontroller-to-import-text-in-swift
I know this is an old question, but if it is of help to anyone, I fixed the console issue by changing the instance type of UIDocumentPickerViewController, entering .open instead of .import
In my case, the behaviour does not change and I can choose a file without losing the expected behaviour.

UIDocumentPickerViewController allows selecting a file the first time the app is run, but not subsequently

My app, when run, creates a json document (using a UIDocument subclass) in its document directory. And then, when opening up the UIDocumentPickerViewController to select a file, if the app has written a new file, the behaviour is as expected.
However, if I run the app again (and overwrite the last created file), the delegate method didPickDocumentsAt doesn't get called, unless I browse around for a few seconds.
What am I missing here?
#IBAction func showDocumentPicker() {
let documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypeJSON as String], in: .import)
documentPicker.allowsMultipleSelection = false
documentPicker.delegate = self
self.present(documentPicker, animated: true, completion: nil)
} //this function is in the initial definition of the class and is connected to a UIBarButton
extension BudgetExportViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print("selected document: \(urls.first)")
print("555555555555555555555555555555")
//document = BudgetExportDocument(fileURL: urls.first!)
// CFURLStartAccessingSecurityScopedResource(urls.first! as CFURL)
// let documentData = try? Data.init(contentsOf: urls.first!)
// let json = try? JSONDecoder().decode(BudgetExportData.self, from: documentData!)
// budgetThisMonth = json
// print("Budgetthismonth")
// print(budgetThisMonth)
// CFURLStopAccessingSecurityScopedResource(urls.first! as CFURL)
}
}
Apparently, the problem was the fact that I was overwriting the same file so many times. Now, if a file with the same name exists, it won't write over it and the UIDocumentPickerViewController behaviour is as expected.

How to extract a zip file and get the extracted components in a Share Extension in Swift

I need to do the following-
I have another app in which i will export the users config(.txt) and contacts(.vcf) in a zip format.
In the second app i have a share extension to get the exported zip and in the share extension, i need to extract the zip file and get both the txt and vcf files and then upload them to a parse server.
I have done till opening the exported zip in the share extension. but i could not get the zip extracted.
I couldn't get the answer in internet.
Here is my ShareViewController
import UIKit
import Social
import Parse
import MobileCoreServices
import SSZipArchive
class ShareViewController: SLComposeServiceViewController {
var requird_data : NSData!
var path : URL!
override func viewDidLoad() {
super.viewDidLoad()
//Parse.setApplicationId("cGFyc2UtYXBwLXdob3N1cA==", clientKey: "")
initUI()
getURL()
textView.delegate = self
textView.keyboardType = .numberPad
}
// override func viewWillAppear(_ animated: Bool) {
// super.viewWillAppear(true)
//
// }
func initUI()
{
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
title = "upup"
navigationController?.navigationBar.tintColor = .white
navigationController?.navigationBar.backgroundColor = UIColor(red:0.97, green:0.44, blue:0.12, alpha:1.00)
placeholder = "Please enter your Phone number"
}
private func getURL() {
let extensionItem = extensionContext?.inputItems.first as! NSExtensionItem
let itemProvider = extensionItem.attachments?.first as! NSItemProvider
let zip_type = String(kUTTypeZipArchive)
if itemProvider.hasItemConformingToTypeIdentifier(zip_type) {
itemProvider.loadItem(forTypeIdentifier: zip_type, options: nil, completionHandler: { (item, error) -> Void in
guard let url = item as? NSURL else { return }
print("\(item.debugDescription)")
OperationQueue.main.addOperation {
self.path = url as URL
SSZipArchive.unzipFile(atPath: url.path!, toDestination: url.path!)
}
})
} else {
print("error")
}
}
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
return true
}
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
override func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
{
let length = ((textView.text)?.characters.count)! + text.characters.count - range.length
let allowedset : CharacterSet = CharacterSet(charactersIn: "0123456789+").inverted as CharacterSet
let filtered = (text.components(separatedBy: allowedset)).joined(separator: "")
return (length<17) && (text == filtered)
}
}
I use SSZipAchive to extract the file. Link : https://github.com/ZipArchive/ZipArchive
I ran the application in the Xcode 9 beta 1. I used the new Files app from simulator to share the zip.
Below is my Share Extensions Info.Plist
I am newbie to share extension so i don't know much about it. All the code above are from bits and pieces from the following tutorials and a little googling.
1.https://www.appcoda.com/ios8-share-extension-swift/
2.https://hackernoon.com/how-to-build-an-ios-share-extension-in-swift-4a2019935b2e
Please guide me.
I use swift 3.
I found out the solution. It was my mistake to give the destination file path for the extracted items to be the same as the source files path. After changing it to the app's documents directory i got it working.
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
SSZipArchive.unzipFile(atPath: url.path!, toDestination: documentsPath)

Create CSV file in Swift and write to file

I have an app I've made that has a UITableView with todoItems as an array for it. It works flawlessly and I have an export button that creates a CSV file from the UITableView data and emails it out:
// Variables
var toDoItems:[String] = []
var convertMutable: NSMutableString!
var incomingString: String = ""
var datastring: NSString!
// Mail alert if user does not have email setup on device
func showSendMailErrorAlert() {
let sendMailErrorAlert = UIAlertView(title: "Could Not Send Email", message: "Your device could not send e-mail. Please check e-mail configuration and try again.", delegate: self, cancelButtonTitle: "OK")
sendMailErrorAlert.show()
}
// MARK: MFMailComposeViewControllerDelegate Method
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
// CSV Export Button
#IBAction func csvExport(sender: AnyObject) {
// Convert tableView String Data to NSMutableString
convertMutable = NSMutableString();
for item in toDoItems
{
convertMutable.appendFormat("%#\r", item)
}
print("NSMutableString: \(convertMutable)")
// Convert above NSMutableString to NSData
let data = convertMutable.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
if let d = data { // Unwrap since data is optional and print
print("NSData: \(d)")
}
//Email Functions
func configuredMailComposeViewController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
mailComposerVC.setSubject("CSV File Export")
mailComposerVC.setMessageBody("", isHTML: false)
mailComposerVC.addAttachmentData(data!, mimeType: "text/csv", fileName: "TodoList.csv")
return mailComposerVC
}
// Compose Email
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.presentViewController(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert() // One of the MAIL functions
}
}
My question is how do I create the same CSV file, but instead of emailing, save it to file? I'm new to programming and still learning Swift 2. I understand that the section of code (data!, mimeType: "text/csv", fileName: "TodoList.csv") creates the file as an attachment. I've looked online for this and trying to understand paths and directories is hard for me. My ultimate goal is to have another UITableView with a list of these 'saved' CSV files listed. Can someone please help? Thank you!
I added the following IBAction to my project:
// Save Item to Memory
#IBAction func saveButton(sender: UIBarButtonItem) {
// Convert tableView String Data to NSMutableString
convertMutable = NSMutableString();
for item in toDoItems
{
convertMutable.appendFormat("%#\r", item)
}
print("NSMutableString: \(convertMutable)")
// Convert above NSMutableString to NSData
let data = convertMutable.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
if let d = data { // Unwrap since data is optional and print
print("NSData: \(d)")
}
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
func writeToFile(_: convertMutable, path: String, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws {
}
}
I was struggling to find a decent simple answer to this for ages.
Here is the best way that I've found to create a csv file and even the directory you want it to be it and write to it.
//First make sure you know your file path, you can get it from user input or whatever
//Keep the path clean of the name for now
var filePath = "/Users/Johnson/Documents/NEW FOLDER/"
//then you need to pick your file name
let fileName = "AwesomeFile.csv"
//You should probably have some data to put in it
//You can even convert your array to a string by appending all of it's elements
let fileData = "This,is,just,some,dummy,data"
// Create a FileManager instance this will help you make a new folder if you don't have it already
let fileManager = FileManager.default
//Create your target directory
do {
try fileManager.createDirectory(atPath: filePath!, withIntermediateDirectories: true, attributes: nil)
//Now it's finally time to complete the path
filePath! += fileName!
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
}
//Then simply write to the file
do {
// Write contents to file
try fileData.write(toFile: filePath!, atomically: true, encoding: String.Encoding.utf8)
print("Writing CSV to: \(filePath!)")
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
}
PS. Just noticed that question is from year ago, but I hope it helps a struggling newbie like myself when they inevitably stumble upon it like I did.
convertMutable can be easily written to disk with either fun writeToFile(_ path: String, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws or func writeToURL(_ url: NSURL, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws. All you have to do is create a path or URL to write the string out to. If you are using iCloud things will be more challenging but for locally stored files you can use let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString to get the root path of the documents directory.
Update: Based on you first comment below here is some added info:
The first issue is that it appears as though you are looking for code you can just paste int your project without really understanding what it does. My appologies if I'm wrong, but if I'm right this is not a good route to take as you will have many issues down the road when things change.
At the bottom of your last code section you are trying to create a function inside a function which is not going to do what you want. The above mentioned functions are the declarations of two NSString functions not functions that you need to create. As NSMutableString is a subclass of NSString you can use those functions on your convertMutable variable.
Another thing you need to deal with is creating a name for the file you want to save, currently you have pasted in the line above that gets the Documents directory but does not have a file name. You will need to devise a way to create a unique filename each time you save a CSV file and add that name to the end of path. Then you can use writeToFile… or writeToURL… to write the string to the desired location.
If you find you don't fully comprehend the code you are adding then consider getting a book or finding classes about Swift (Coursera.org has a class that may be of use). There are plenty of resources out there learn the basics of software development and Swift, it will take effort and time but it will be worth it in the end if this is something you want to pursue.