How can app extension access files in containing app Documents/ folder - ios

In the app extension is there a way to get images generated from the containing app which is store in /var/mobile/Containers/Data/Application//Documents// folder?

In order to make files available to app extension you have to use Group Path, as App Extension can't access app's Document Folder, for that you have follow these steps,
Enable App Groups from Project Settings-> Capabilities.
Add a group extension something like group.yourappid.
Then use following code.
NSString *docPath=[self groupPath];
NSArray *contents=[[NSFileManager defaultManager] contentsOfDirectoryAtPath:docPath error:nil];
NSMutableArray *images=[[NSMutableArray alloc] init];
for(NSString *file in contents){
if([[file pathExtension] isEqualToString:#"png"]){
[images addObject:[docPath stringByAppendingPathComponent:file]];
}
}
-(NSString *)groupPath{
NSString *appGroupDirectoryPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:group.yourappid].path;
return appGroupDirectoryPath;
}
You can add or change the path extension as per your image extensions you are generating.
Note - Remember you need to generate the images in the group folder rather than in Documents Folder, so it is available to both app and extension.
Cheers.
Swift 3 Update
let fileManager = FileManager.default
let url = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "YOUR_GROUP_ID")?.appendingPathComponent("logo.png")
// Write to Group Container
if !fileManager.fileExists(atPath: url.path) {
let image = UIImage(named: "name")
let imageData = UIImagePNGRepresentation(image!)
fileManager.createFile(atPath: url.path as String, contents: imageData, attributes: nil)
}
// Read from Group Container - (PushNotification attachment example)
// Add the attachment from group directory to the notification content
if let attachment = try? UNNotificationAttachment(identifier: "", url: url!) {
bestAttemptContent.attachments = [attachment]
// Serve the notification content
self.contentHandler!(self.bestAttemptContent!)
}

Related

Get the names of files in an iCloud Drive folder that haven't been downloaded yet

I’m trying to get the names of all files and folders in an iCloud Drive directory:
import Foundation
let fileManager = FileManager.default
let directoryURL = URL(string: "folderPathHere")!
do {
let directoryContents = try fileManager.contentsOfDirectory(at: directoryURL, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
for url in directoryContents {
let fileName = fileManager.displayName(atPath: url.absoluteString)
print(fileName)
}
} catch let error {
let directoryName = fileManager.displayName(atPath: directoryURL.absoluteString)
print("Couldnt get contents of \(directoryName): \(error.localizedDescription)")
}
It appears that any iCloud files that haven’t been downloaded to the device don’t return URLs.
I know I can check if a path contains a ubiquitous item when I already know the path with the code below (even if it isn’t downloaded):
fileManager.isUbiquitousItem(at: writePath)
Is there a way to get the URLs & names of those iCloud files without downloading them first?
The directory URL is a security-scoped URL constructed from bookmark data in case that makes any difference (omitted that code here for clarity).
Thanks
Found the answer. I was skipping hidden files with ".skipsHiddenFiles", but the non-downloaded files are actually hidden files, named: ".fileName.ext.iCloud".
Remove the skips hidden files option now works as expected.
You need to use a NSFileCoordinator to access the directory in iCloud Storage, and then normalize placeholder file names for items that haven't been downloaded yet:
let iCloudDirectoryURL = URL(...)
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
fileCoordinator.coordinate(
readingItemAt: iCloudDirectoryURL,
options: NSFileCoordinator.ReadingOptions(),
error: nil
) { readingURL in
do {
let contents = try FileManager.default.contentsOfDirectory(
at: readingURL, includingPropertiesForKeys: nil
)
for url in contents {
print("\(canonicalURL(url))")
}
} catch {
print("Error listing iCloud directory: '\(error)'")
}
}
func canonicalURL(_ url: URL) -> URL {
let prefix = "."
let suffix = ".icloud"
var fileName = url.lastPathComponent
if fileName.hasPrefix(prefix), fileName.hasSuffix(suffix) {
fileName.removeFirst(prefix.count)
fileName.removeLast(suffix.count)
var result = url.deletingLastPathComponent()
result.append(path: fileName)
return result
} else {
return url
}
}

Save file in iCloud Drive Documents (user Access)

I want to save a PDF file in the iCloud Drive. The user should have access to the file over his iCloud Drive App.
At the moment I can save a file in the iCloud Drive but it is in a hidden directory.
struct DocumentsDirectory {
static let localDocumentsURL: NSURL? = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: .userDomainMask).last! as NSURL
static var url: NSURL? = FileManager.default.url(forUbiquityContainerIdentifier: nil)! as NSURL
static let iCloudDocumentsURL: NSURL? = url?.appendingPathComponent("Documents")! as! NSURL
}
With this code I get the hidden directory in the iCloud Drive (which is specific to my app).
Now my question is: can I save the file in the standard documents directory in the iCloud Drive? Or can I create a folder for the documents from my app, which the user can see?
Try saving the file under the 'Documents' directory, this ensure the file is saved under the users public directory and is visible in the iCloud app as well.
func setupiCloudDriveForFileExport() {
if let iCloudDocumentsURL = NSFileManager.defaultManager().URLForUbiquityContainerIdentifier(nil)?.URLByAppendingPathComponent("Documents") {
if (!NSFileManager.defaultManager().fileExistsAtPath(iCloudDocumentsURL.path!, isDirectory: nil)) {
do {
try NSFileManager.defaultManager().createDirectoryAtURL(iCloudDocumentsURL, withIntermediateDirectories: true, attributes: nil)
}catch let error as NSError {
print(error)
}
}
}
}
This code checks for the existance of the Documents directory on iCloud and if not creates a new one.

How to detect where the on demand resources are located after being downloaded?

I implement the app thinning in my project by getting the reference from below two answers:-
https://stackoverflow.com/a/33145955/988169
https://stackoverflow.com/a/31688592/988169
How to detect where the on demand resources are located after being downloaded?
Unless you specified a bundle when you initialized the resource request, the resources are placed in the main bundle by default. So for example, you could access them this way, if appropriate to your type of resource:
NSString *path= [[NSBundle mainBundle] pathForResource:#"movie" ofType:#"mp4" ];
Finally I've found, how to get a path of on demand resources location:
func preloadResourcesWithTag(tagArray: Array<Any>, resourceName: String ){
let tags = NSSet(array: tagArray)
let resourceRequest:NSBundleResourceRequest = NSBundleResourceRequest(tags: tags as! Set)
resourceRequest.beginAccessingResources { (error) in
OperationQueue.main.addOperation{
guard error == nil else {
print(error!);
return
}
print("Preloading On-Demand Resources ")
let _path1 = resourceRequest.bundle.path(forResource: "img2", ofType: "jpg")
let img = UIImage(contentsOfFile: _path1!)}
So, the real location of on demand resources is here:
/Users/admin/Library/Developer/CoreSimulator/Devices/250101ED-DFC5-424C-8EE1-FC5AD69C3067/data/Library/OnDemandResources/AssetPacks/com.ae.OfflineMapSwiftV1/1629348341761109610/com.##.OfflineMapSwiftV1.asset-pack-000000G31NNOJ.assetpack/img2.jpg
To expand upon saswanb's answer, the pathForResource you use has to match the folder or filename you added to the ODR tag. If you add a reference to a folder to an ODR, you can search for that folder name, but not the contents of the folder.
NSString *path= [[NSBundle mainBundle] pathForResource:#"ODRFolder1" ofType:nil ];
But if you add the contents of the folder to the ODR tag, then you can search for each file individually.

Can't get data from file stored in asset catalog

I am storing several .svg files in an asset catalog.
To retrieve an asset I use the following code:
NSDataAsset *asset = [[NSDataAsset alloc] initWithName:#"p"];
When running this code I get the following log message:
CoreUI: attempting to lookup a named data 'p' with a type that is not a data type in the AssertCatalog
The name of the file is 'p.svg' which is stored in a folder with 'dataset' as extension. I tried using other extensions for the file but nothing works. I always get the same error and the asset is nil.
This may be related to a problem I'm having, in that the UTI I'm identifying my assets as being could run up the UTI dependancies and find public.image, thus making CoreUI think the asset is an image type and not a data type.
SVGs inherit from public.image, so you will need to set the File Type to something that doesn't inherit from public.image. Having it be just public.data will suffice.
Just reference the svg or whatever file in your project like .h/.m:
NSString *path = [[NSBundle mainBundle] pathForResource:#"p" ofType:#"svg"];
[NSData dataWithContentsOfFile:path];
If path is nil, try:
In the Xcode target "Build Phases" add the file under "Copy Bundle Resources"".
I also have this issue; I wanted to provide images etc from local assets store. Dragging the files into the catalog I would fetch them like so - here a String:
extension NSString {
class func string(fromAsset: String) -> String {
guard let asset = NSDataAsset.init(name: fromAsset) else {
return String(format: "Unable to locate asset:\n%#", fromAsset)
}
let data = NSData.init(data: (asset.data))
let text = String.init(data: data as Data, encoding: String.Encoding.utf8)
if fromAsset.hasSuffix(".md"), let html = try? Down(markdownString: text!).toHTML()
{
let htmlDoc = String(format: "<html><body>%#</body></html>", html)
let data = Data(htmlDoc.utf8)
if let attrs = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
return attrs.string
}
}
return text!
}
}
the premise is they have to exist. I think a per type / class extension would make retrieval simple. Eventually a generic object extension could dispatch to the proper extension, as here, you'd have to know what it was, so the decision which to use is currently a manual one.

Where Can if find the NSBundle in the applicaton for an ios app?

I am running a template of an app. I want to be able to change the contents of the NSBundle, which include a list of names. How would I be able to find from this source?
let paths = NSBundle.mainBundle().pathsForResourcesOfType(
"png", inDirectory: nil) as! [String]
// get image filenames from paths
for path in paths {
if !path.lastPathComponent.hasPrefix("AppIcon") {
allCountries.append(path.lastPathComponent)
}
}
//try this code
NSURL *rtfUrl = [[NSBundle mainBundle] URLForResource:#"paylines" withExtension:#".txt"];

Resources