How can I get the filename of a shared object in iOS share-extension - ios

Basically I want to retrieve files (e.g. pdf) from other apps (e.g. dropbox) to store and modify later.
I wrote a share-extension for that task and could iterate over NSExtensionItem's and can catch out my files - but I have no idea about their original filename.
I noticed that other apps got the filename - but they're called with the "open with" function in iOS.
So how I get the filename in my share-extension?
Thanks in advance.

You have the filename as lastPathComponent in the URL received:
itemProvider!.first!.loadItemForTypeIdentifier(kUTTypeImage as String, options: nil, completionHandler: {
(provider, error) in
let url = provider as! NSURL
println(url)
let data = NSData(contentsOfURL: url)
let name = url.lastPathComponent!
...
// 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!.completeRequestReturningItems([], completionHandler: nil)
})

Related

Access Windows/Mac Shared Folder Locally With smb from iOS

I am trying to build an app where I am able to access(read/write) windows/mac shared folders in my local network with swift.
Is there any possible way to do that with swift?
There is an App in the App Store called "FileExplorer" https://apps.apple.com/de/app/fe-file-explorer-file-manager/id510282524 where you can access these shared folders, but I do not know how they programmed this and with which language.
I also tried to access my shared folders via this App and yes it worked I can see my shared folders on my Phone.
But there needs to be a way to do it with swift...
I already tried different things(code bellow).
In the code bellow I tried to access the shared folder of my second mac and write the Text "Write this text to the fileURL as text in iOS using Swift" into the file named "Test.txt" and after that I want to read the same file again.
#IBAction func Button(_ sender: UIButton)
{
var uc = URLComponents()
uc.scheme = "smb"
uc.user = "user"
uc.password = "password"
uc.host = "ip-adress"
uc.path = "document-directory"
// Save data to file
let fileName = "Test"
let url = uc.url
//let DocumentDirURL = URL(fileURLWithPath: "/Users/f/d/t/App/Assets/Apps/TestApp")
let DocumentDirURL = try! URL(resolvingAliasFileAt: url!)
let fileURL = DocumentDirURL.appendingPathComponent(fileName).appendingPathExtension("txt")
print("FilePath: \(fileURL.path)")
let writeString = "Write this text to the fileURL as text in iOS using Swift"
do {
// Write to the file
try writeString.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8)
} catch let error as NSError {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
var fullString: String = "" // Used to store the file contents
do {
// Read the file contents
fullString = try String(contentsOf: fileURL, encoding: .utf8)
} catch let error as NSError {
print("Failed reading from URL: \(fileURL), Error: " + error.localizedDescription)
}
print("File Text: \(readString)")
}
If I run the code as shown, he always gives me the error
"smb scheme is not supported" and then some additional errors that he can not write/read the file because he can not access it.
When I change the code and only search on the device I am programming on and run the simulator to search for this file everything works fine. So I have problems with "smb".
Thank you for every helpful answer.
you can use amsmb2 library to do this
you can extend the template class provided to connect to download files, write files, list directories -> on an smb share
everything is asynchronous from memory, with the librarys calls including hooks for progress updates on the ui main thread etc
i believe the amsmb2 library function your after might be uploadItem
iOS 13 includes SMB (server message block protocol) support
https://9to5mac.com/2019/06/17/ios-13-beta-2-enables-smb-server-connectivity-in-the-files-app/

UNNotificationAttachment failing to attach image

So the following code is being used to attach an image from local storage url of an image. I check in Terminal to see if the image is stored and it does store the image without any issues. So ruling out any issues with the url itself.
do {
let attachment = try UNNotificationAttachment(identifier: imageTag, url: url, options: nil)
content.attachments = [attachment]
} catch {
print("The attachment was not loaded.")
}
Other code that goes with the creation of UserNotification works fine as it triggers at the correct specified time.
The code always goes to the catch block. Can anybody please point me to the mistake if there is any in the implementation. Please help. Thank you.
Edit: with print(error.localizedDescription) error message is Invalid attachment file URL.
Edit2 : with print(error) error message is Error Domain=UNErrorDomain Code=100 "Invalid attachment file URL" UserInfo={NSLocalizedDescription=Invalid attachment file URL}
I have found the real issue behind it. In apple documentation it is written that the url should be a file url and because of which you might be facing issue.
To solve this I have added image to temporary directory and then added to UNNotificationAttachment .
Please find the code below. (For my use case I was getting an imageURL)
extension UNNotificationAttachment {
/// Save the image to disk
static func create(imageFileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let tmpSubFolderURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
do {
try fileManager.createDirectory(at: tmpSubFolderURL!, withIntermediateDirectories: true, attributes: nil)
let fileURL = tmpSubFolderURL?.appendingPathComponent(imageFileIdentifier)
try data.write(to: fileURL!, options: [])
let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL!, options: options)
return imageAttachment
} catch let error {
print("error \(error)")
}
return nil
}
}
data in the argument of this function is Data of image . Below is how did I call this method.
let imageData = NSData(contentsOf: url)
guard let attachment = UNNotificationAttachment.create(imageFileIdentifier: "img.jpeg", data: imageData!, options: nil) else { return }
bestAttemptContent?.attachments = [attachment]
I also found important and quite weird behaviour of initialization of UNNotificationAttachment object. It was happening to me that I was getting error:
"Invalid attachment file URL"
But it was not happening always. It was happening in case when I used for some notifications same image for attachment. When I made a standalone copy of image for each attachment, it never happened. Then I checked directory when images should be copied ( because I wanted to clean it up ), but I was surprised that there were no images.
It seems that UNNotificationAttachment initialization process is deleting files at given URLs. So when you try to reuse some images, they can be deleted ( probably asynchronously, because I was checking existence of that images and it always returned me true - that file exists ). But UNNotificationAttachment ended up with error you can see above. In my opinion only logic explanation of this error is that file at given URL is deleted during the process of UNNotificationAttachment initialization.
Apple actually makes a statement in their documentation (https://developer.apple.com/documentation/usernotifications/unnotificationattachment)
Apple Docs for UNNotificationAttachment:
...
The system validates attachments before displaying the associated notification.
...
Once validated, attached files are MOVED into the attachment data store so that they can be accessed by all of the appropriate
processes. Attachments located inside an app’s bundle are copied
instead of moved.
So, above answers with copying an attachment (image) first into a temporary location before adding as an attachment seem to be the expected solution.
In Swift 5. The below code works for me. Hope this helps somebody.
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
let imageURL = URL(fileURLWithPath: paths.first!).appendingPathComponent("\(fileName).jpg")
let image = UIImage(contentsOfFile: imageURL.path)
let imageData = image?.pngData()
if let unwrappedImageData = imageData, let attachement = try? UNNotificationAttachment(data: unwrappedImageData, options: nil) {
content.attachments = [ attachement ]
}

sending a database/Realm using Multipeer Connectivity

I have an admin and a user app. Basically, I will build the database(realm) using Admin app then send the whole database to the user app.
Here is the path to my database:
let directory: NSURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.example.file")!
let fileURL = directory.URLByAppendingPathComponent("test.realm")
//What should I do to the path so I can send it via MultiPeerConnectivity : MCSession
try session.sendData("my Realm File To Send", toPeers: session.connectedPeers, withMode: .Reliable)
I'd recommend to write a compacted copy of your Realm file via writeCopyToURL(fileURL: _, encryptionKey: _). Read more about Realm's file size growth behavior to understand why that is a good idea.
You can then simply read the file contents via NSData(contentsOfURL: _).
let parentURL = fileURL.URLByDeletingLastPathComponent!
let compactedFileURL = parentURL.URLByAppendingPathComponent("compact.realm")
try! realm.writeCopyToURL(compactedFileURL)
let data = NSData(contentsOfURL: compactedFileURL)!

iOS import files into my application

I am creating an iOS application through which users can print the files on their device. From my application, I can access the files on the device though the DocumentPicker provided by other apps such as iCloud Drive, Dropbox, etc.
Now, I want to add a functionality where user can share the file with my application through an other application. I created an Action Extension for that.
For example, if I select an Image in the Photos application and select Share I get my extension in the Share sheet and when I select it, I also get the URL of the file. Next, I am creating a zip file of this file to send it to my server. But the issue is, the zip file is always empty. The code I am using is as below:
In Action Extension's viewDidLoad()
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
itemProvider.loadItemForTypeIdentifier(kUTTypeImage as String, options: nil,
completionHandler: { (image, error) in
NSOperationQueue.mainQueue().addOperationWithBlock {
print("Image: \(image.debugDescription)")
//Image: Optional(file:///Users/guestUser/Library/Developer/CoreSimulator/Devices/00B81632-041E-47B1-BACD-2F15F114AA2D/data/Media/DCIM/100APPLE/IMG_0004.JPG)
print("Image class: \(image.dynamicType)")
//Image class: Optional<NSSecureCoding>
self.filePaths.append(image.debugDescription)
let zipPath = self.createZip(filePaths)
print("Zip: \(zipPath)")
}
})
}
And my createZip function is as follows:
func createZipWithFiles(filePaths: [AnyObject]) -> String {
let zipPath = createZipPath() //Creates an unique zip file name
let success = SSZipArchive.createZipFileAtPath(zipPath, withFilesAtPaths: filePaths)
if success {
return zipPath
}
else {
return "zip prepation failed"
}
}
Is there a way that I can create a zip of the shared files?
Your primary issue is that you are blindly adding image.debugDescription to an array that is expecting a file path. The output of image.debugDescription isn't at all a valid file path. You need to use a proper function on the image to obtain the actual file path.
But image is declared to have a type of NSSecureCoding. Based on the output of image.debugDescription, it seems that image is really of type NSURL. So you need to convert image to an NSURL using a line like:
if let photoURL = image as? NSURL {
}
Once you have the NSURL, you can use the path property to get the actual needed path.
So your code becomes:
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
itemProvider.loadItemForTypeIdentifier(kUTTypeImage as String, options: nil,
completionHandler: { (image, error) in
if let photoURL = image as? NSURL {
NSOperationQueue.mainQueue().addOperationWithBlock {
let photoPath = photoURL.path
print("photoPath: \(photoPath)")
self.filePaths.append(photoPath)
let zipPath = self.createZip(filePaths)
print("Zip: \(zipPath)")
}
}
})
}
Hint: Never use debugDescription for anything other than a print statement. Its output is just some string that could contain just about any information and that output can change from one iOS version to the next.

How do I store private app data on Google Drive with my iOS app?

I have an iOS app that has a local database. I'd like to back that up for users who choose to sign in with Google. The web (https://developers.google.com/drive/web/appdata) and android (https://developers.google.com/drive/android/appfolder) have guides on how to do this, but I can't find a similar one for iOS. Does it exist?
If you already have code to upload a file to the user's Drive account, it is very easy to switch to uploading into the private app folder instead. When making the Files.insert call, the file will be added to all of the folders listed in the parents[] array. (If this array is empty, by default the file is added to the root folder.) To upload the file into the private app data folder, simply set the parents[] array to appfolder. You have to do this at the same time as uploading the file, because once it has been uploaded the file can't be moved between the user's drive and your app's private data folder.
(Note: you may need to use the regular REST API to do this, because Google's Drive API for iOS docs do not show any methods for actually uploading a new file to Drive.)
Check this how this is working for me in swift 4.2 and above:
let googleDrive: GTLRDrive_File = GTLRDrive_File()
googleDrive.name = "name.json"
googleDrive.parents = ["appDataFolder"]
let uploadParam: GTLRUploadParameters = GTLRUploadParameters(data: data, mimeType: "application/json")
uploadParameters.shouldUploadWithSingleRequest = true;
let queryDrive: GTLRDriveQuery_FilesCreate = GTLRDriveQuery_FilesCreate.query(withObject: metadata, uploadParameters: uploadParam)
queryDrive.fields = "id"
self.service.executeQuery(queryDrive) { (result, response, error) in
if let file = response as? GTLRDrive_File {
if (error == nil) {
print(file.identifier)
/// your code here
} else {
// handle error part
}
}
else {
//handle exception part
}
}
"data" the json data you get this like below
let param = [["key": "value"], ["key": "value"], ["key": "value"]]
let data = try JSONSerialization.data(withJSONObject: param, options: .prettyPrinted)

Resources