swift share with function on completion - ios

My app can share files with other apps, but the problem is that I need to delete the files after sharing them... I tried using the onCompletion function as below:
let activityVC = UIActivityViewController(activityItems: objects, applicationActivities: nil)
view.present(activityVC, animated: true) {
try! FileManager.default.removeItem(at: targetURL)
}
The problem is that the onCompletion function executes after the action view disappears not after the whole process of sharing is finished, that's why if I delete the file and the sharing process is still ongoing it will be aborted.. an example is when using telegram for sharing; since telegram asks you to select a contact to send the file to, by that time the view has already disappeard (the function is executed and deleted the file before sharing it)...

It's far too soon to do anything in the completion handler of presenting the controller.
Set the completionWithItemsHandler property of the UIActivityViewController. This will get called when the sharing process is complete.
activityVC.completionWithItemsHandler = { (activityType: UIActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) -> Void in
if completed == true {
try! FileManager.default.removeItem(at: targetURL)
}
}

Related

AWSS3TransferUtilityMultiPartUploadTask - Progress value not updated returning from background

I'm using the AWS iOS SDK to upload files to S3. I am using AWSS3TransferUtility because I want to allow background uploads.
The background task is working - a large file can successfully upload while backgrounded. The issue is, when I bring my app back to the foreground, the task.result.progress.fractionCompleted value remains at the value from before being backgrounded. And if I foreground my app before the upload is complete, the progress value will remain at that value until it is done, then jump up to 1.0.
When the app comes back to the foreground, I call enumerateToAssignBlocksForUploadTask:multiPartUploadBlocksAssigner:downloadBlocksAssigner: on my TransferUtility class, and I reassign the progress and completion handlers.
Does anyone know what may cause that value not to update? I'm not sure how to resume updating my progress bar because of this. Thanks!
Edit: Here is where I start the upload process. I have a wrapper around the AWS Task that holds onto the progress and completion handlers.
func upload(storagePath: String, sourceURL: URL, _ progressCompletion: #escaping ProgressCompletionCallback)-> UploadTask {
let expression = AWSS3TransferUtilityMultiPartUploadExpression()
expression.progressBlock = {(task, progress) in
DispatchQueue.main.async(execute: {
print("Progess: \(progress)")
progressCompletion(false, Float(progress.fractionCompleted), nil)
})
}
var completionHandler: AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock
completionHandler = { (task, error) -> Void in
DispatchQueue.main.async(execute: {
print("Completed!")
progressCompletion(true, Float(task.progress.fractionCompleted), error)
})
}
let awsTask = transferUtility.uploadUsingMultiPart(fileURL: sourceURL,
bucket: Constants.bucketName,
key: storagePath,
contentType: "text/plain",
expression: expression,
completionHandler: completionHandler)
return UploadTask(task: awsTask,
progressBlock: expression.progressBlock!,
completionBlock: completionHandler)
}
I am facing the same issue when downloading files. Here is a link to the issue i opened on their github page, atleast for the case of downloading files. They don't receive callbacks from the NSURLSession class thats being used. It is probably something similar in your case.

Load video in share extension with Swift 4

I'm working on an iOS app that provides a share extension supposed to upload a video from Photo Library to a server.
I cannot find any example on how to handle the video.
Here is my code:
if itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
print ("A movie has been chosen")
itemProvider.loadItem(forTypeIdentifier: "public.movie", options: nil, completionHandler: { (data, error) in
print ("Loaded the video")
if let video = data as? Data {
// Do stuff with the movie now.
print ("The movie should be loaded now")
}
self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
})
}
The first print is printed, so I'm actually in the case of a public.movie item. But not the second and third print.
Can someone, please, tell me how the movie is passed and how I can handle it?
Thanks
With the help of app groups you can do work around : http://www.atomicbird.com/blog/sharing-with-app-extensions
Get video from library and save in Data in userdefaults.
Transfer the USerDefault key to via appschems and intercept it open URL method in app delgate of app.
With the same key load that video and forward it to server.
For references of app groups and Share extension : https://medium.com/#abhishekthaplithapliyal/ios-share-extension-930ba1ad3c3d
loadItem runs asynchronously, and when you're working with a video file, the file size is much larger than images, so loadItem does not have time to complete itself before self.extensionContext?.completeRequest runs and closes the share extension.
The solution I found involved the following:
(1) Create a boolean variable that measures whether the loadItem function is complete, or not.
var loadItemDone: Bool = false
(2) Write a recursive function that periodically checks to see whether the loadItem function has completed. If so, then run the completeRequest call. If not, continue recursion. In my case, I check for completion once per second, which is why it says "after: 1".
func waitForLoadItemToComplete(after seconds: Int) {
let deadline = DispatchTime.now() + .seconds(seconds)
DispatchQueue.main.asyncAfter(deadline: deadline) {
if self.loadItemDone == false{
self.waitForLoadItemToComplete(after: 1)
}else{
let alert = UIAlertController(title: "Success!", message: "Your file is uploaded!", preferredStyle: .alert)
let action1 = UIAlertAction(title: "Yeah!", style: .cancel) { (action) in
print("User acknowledged file uploaded.")
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
alert.addAction(action1)
self.present(alert, animated: true, completion: nil)
}
}
}
(3) Set the loadItemDone variable to true, when loadItem completes:
if attachment.hasItemConformingToTypeIdentifier("com.apple.quicktime-movie") {
// Do stuff with the movie now.
self.loadItemDone = true
}
'public.movie' didn't work for me. I had to use 'com.apple.quicktime-movie'
Also, DispatchQueue did not work for me, which is why I had to hack a bit... I would love to hear a better solution from anyone more familiar with threads in share extensions...

IOS swift how to know when activityViewController has been successfully used

I have a tableView that utilizes the UIActivityViewController so users can share content on other 3rd party networks Facebook, twitter, text message etc. I have a function called IncreaseShareByOne() and it's purpose is to count the number of times that something has been shared . My problem is that right now that function fires of every time the UIActivityViewController is clicked . Is there someway that I can find out if a user really shared something so that I can use that function correctly ? becomes sometimes you open the UIActivityViewController and decide not to share anything so I want to catch and not use that function. I am new to swift and am using version 3 .
func sharedShareAction(controller: UIViewController, sender: UIButton) {
controller.present(activityViewController,animated: true,completion: nil)
IncreaseShareByOne()
}
You can add a completion handler to your UIActivityViewController. In the completion handler, check whether the user submitted using the completed value. You'll probably want to do something like this:
func sharedShareAction(controller: UIViewController, sender: UIButton) {
controller.present(activityViewController,animated: true, completion: nil)
activityViewController.completionWithItemsHandler = { activity, completed, items, error in
if !completed {
// handle task not completed
return
}
IncreaseShareByOne()
}
}
Check the API docs for more info.

Presenting UIActivityViewController casue screen/ui freeze

I try to present UIActivityViewController to my app users but when I do my app sometimes, not always but often, freeze. What I mean is that activity view is on screen but do not respond to user interaction at all. You can't scroll available items, you cannot select any activity item, you cannot click Cancel etc
The way I invoke it is very simple and basic:
func presentSystemShareNotification(notification : Notification) {
if let vURL = notification.userInfo?["videoURL"] {
let activityViewController = UIActivityViewController(activityItems: [vURL], applicationActivities: nil)
activityViewController.excludedActivityTypes = [UIActivityType.airDrop]
naviController.topViewController?.present(activityViewController, animated: true, completion: {() in
print("done presenting")
})
}
}
When ui freezed completion handler is not called. But app itself works, timers are called, ui manipulation like view alpha decrese works and you can see view dim. Any ideas or at least tips what could cause this behavior? Thank you in advance.

Call a file-download function from multiple View Controllers; how to return results TO those VC's?

My app will need to download files from my website from several different places in the app, so it seems to make sense to write the function to accomplish the download once, put it in its own class, and call that function from each ViewController. So far, so good, things work. The download is happening, and the downloaded file will print correctly.
The problem comes when the download function goes to send a "success" or "failed" message back to the ViewController that called it, so that the VC can then react accordingly -- update the display, close out the download dialog, whatever. How to make that happen is where I'm stuck.
What I have:
Each of ViewControllerTwo and ViewControllerThree (which are identical for now, other than requesting different files from my server) calls the download function thus:
Downloader.load(url: urlForFileA!, to: localDestinationFileA, callingViewControllerNumber: 2)
The code for the downloader function (which is currently synchronous, but will eventually become asynchronous) looks like this (in its own Downloader class):
class func load(url: URL, to localUrl: URL, callingViewControllerNumber: Int) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Got a file, might be a 404 page...
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success downloading: \(statusCode)")
if statusCode == 404 {
// ERROR -- FILE NOT FOUND ON SERVER
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .fileNotFound, errorMessage: "File not Found, 404 error")
}
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl)
// SUCCESS!
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .success, errorMessage: "")
} catch (let writeError) {
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .movingError, errorMessage: "\(writeError)")
}
} else {
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .downloadFailed, errorMessage: "Grave Unexplained Failure")
}
}
task.resume()
}
This part works.
The returnToCaller function is an admittedly ugly (okay, very, very ugly) way to send something back to the calling ViewController:
class func returnToCaller(sourceIdent : Int, successStatus : downloadSuccessStatusEnum, errorMessage : String) {
switch sourceIdent {
case 2:
ViewControllerTwo().returnFromDownload(successStatus: successStatus, errorMessage: errorMessage)
case 3:
ViewControllerThree().returnFromDownload(successStatus: successStatus, errorMessage: errorMessage)
default:
fatalError("Unknown sourceIdent of \(sourceIdent) passed to func returnToCaller")
}
}
The problem is that when that returnFromDownload function in the original ViewController is called, it isn't aware of anything in the VC that's loaded -- I go to change the background color of a label, and get a runtime error that the label is nil. The label exists, but this call into the ViewController code is happening in isolation from the running, instantiated VC itself. (Probably the wrong vocabulary there -- apologies.) The code runs and can print but errors out when interacting with anything in the View itself.
The more I work with this, the less confident I am that I'm on the right track here, and my limited experience with Swift isn't enough to see what needs to be happening so that the download function can do all its work "over here" and then return a success/failure message to the calling VC so that the VC can then work with it.
This question seems to be asking something similar; the one answer there doesn't address my (nor, I think, his) root question of how to get code within the presented VC running again with the results of what happened outside the VC (manager approval in his case, download in mine).
Not asking for my code to be rewritten (unless it's a quick fix), but needing to be pointed in the right direction. Many thanks!
What you want can be accomplished pretty easily with a closure.
The first step is to add another parameter to your load method and remove your callingViewController param:
class func load(url: URL, to localUrl: URL, completion: (downloadSuccessStatusEnum, String) -> Void)
This will allow you to call completion instead of returnToCaller when your method completes like so:
DispatchQueue.main.async {
completion(.success, "Insert your error message here")
}
Lastly, to call this method you simply need to call the method like so in your VCs:
Downloader.load(url: nameOfYourURL, to: destinationName) { statusEnum, errorString in
// Insert your code you want after the request completes here
}

Resources