How to manage UIActivityViewController completion? - ios

to share an image, i swipe it off screen.
What's happening now is:
i swipe the image
it presents the UIActivityViewController
i choose an activity
the activity shows up modally
if i cancel the activity:
the image is on screen (the view did load)
the image move back in
what i need:
i cancel the activity
the image is not on screen
the image move back in
How can i do this ?
here is my code:
let activityViewController = UIActivityViewController(activityItems: [imageToShare], applicationActivities: nil)
self.present(activityViewController, animated: true, completion: {
print("presented")
//self.grids.center = self.offScreenPosition
print("position share is \(self.grids.center)")
})
switch deviceOrientation {
case "portrait":
activityViewController.completionWithItemsHandler = {(UIActivityType: UIActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in
if !completed {
print("cancelled")
self.moveViewVertically(.backIn, range: self.verticalRange)
}
if completed {
print("completed")
self.moveViewVertically(.backIn, range: self.verticalRange)
}
}
case "landscape":
activityViewController.completionWithItemsHandler = {(UIActivityType: UIActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in
if !completed {
print("cancelled")
self.moveViewHorizontally(.backIn, range: self.horizontalRange)
}
if completed {
self.moveViewHorizontally(.backIn, range: self.horizontalRange)
}
}
default:
break
}
prints are there to see what's happening.
it' my first app.
thank you for your help.

Do you want the view move back before activityViewController dismiss? you can try this: custom a subclass of UIActivity,and then
- (void)performActivity {
// move your view back in
[self activityDidFinish:YES];
}

problem is solved.
in the moveView method, i wrote center.y -= 1 to move the view. ie an iteration over a desired range.
now i do:
UIView.animate(withDuration: 0.5) {
self.grids.transform = CGAffineTransform(translationX: 0, y: -self.view.frame.height)}
it works as expected.

Related

UIActivityViewController: Close button in share sheet becomes transparent

When I use UIActivityViewController to share an image the close button becomes transparent. I can click on its frame button but it is invisible for the user. And you cannot click outside to close on the sheet.
let activityItemMetadata = LinkMetadataManager(qrImage: image)
let activityVC = UIActivityViewController(
activityItems: [activityItemMetadata],
applicationActivities: nil)
activityVC.completionWithItemsHandler = {(activityType: UIActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in
}
activityVC.activityItemsConfiguration = [
UIActivity.ActivityType.mail,
UIActivity.ActivityType.copyToPasteboard,
UIActivity.ActivityType.airDrop,
UIActivity.ActivityType.message
] as? UIActivityItemsConfigurationReading
activityVC.isModalInPresentation = false
self.present(activityVC, animated: true)
Close button appear like this:
Most of your UIActivityViewController code is wrong.
You should not be creating a LinkMetadataManager from the image and then using it as the item you wish to share.
You are trying to create an array of activities and setting those as the activity configuration. This is all wrong.
Your code should look more like the following:
let activityVC = UIActivityViewController(activityItems: [ image ], applicationActivities: nil)
activityVC.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
if completed || activityType == nil {
// activity view is being dismissed
}
}
present(activityVC, animated: true)
If you have a need to exclude certain activities you can add a line like the following:
activityVC.excludedActivityTypes = [ .assignToContact ] // Optionally exclude specific activities
If you want a bit more control you make use of UIActivityItemsConfiguration. The following is an example:
let configuration = UIActivityItemsConfiguration(objects: [ image ])
configuration.perItemMetadataProvider = { (index, key) in
switch key {
case .linkPresentationMetadata:
// Maybe make use of your LinkMetadataManager class here
var info = LPLinkMetadata()
info.title = "Some Title"
return info
default:
return nil
}
}
let activityVC = UIActivityViewController(activityItemsConfiguration: configuration)
activityVC.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
if completed || activityType == nil {
// activity view is being dismissed
}
}
present(activityVC, animated: true)

Swift 3 - Display alert controller asynchronously with long function running in background

I am using Swift 3.
The behavior I am trying to do is: the user clicks on a button, a spinning gear alert controller displays while it kicks off a long-running function. Once that function is done executing, the spinning gear goes away and the view controller dismisses.
The code below kicks off the doProcessing function but doesn't display the spinning gear until about a second before the view dismisses. So this isn't quite right.
func displaySpinningGear() {
print("display spinning gear")
// show the alert window box
let activityAlertController = UIAlertController(title: "Processing", message: "Please wait while the photo is being processed.", preferredStyle: .alert)
//create an activity indicator
let indicator = UIActivityIndicatorView(frame: activityAlertController.view.bounds)
indicator.autoresizingMask = [.flexibleWidth, .flexibleHeight]
indicator.hidesWhenStopped = true
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
//add the activity indicator as a subview of the alert controller's view
activityAlertController.view.addSubview(indicator)
indicator.isUserInteractionEnabled = false // required otherwise if there buttons in the UIAlertController you will not be able to press them
indicator.startAnimating()
print("start animating")
self.present(activityAlertController, animated: true, completion: nil)
}
func onButtonClick() {
self.displaySpinningGear()
DispatchQueue.main.async {
self.doProcessing() // long running function
}
if let viewController = presentingViewController {
// This block will dismiss both current and a view controller presenting current
viewController.dismiss(animated: true, completion: nil)
}
else {
// This block will dismiss only current view controller
self.dismiss(animated: true, completion: nil)
}
}
The code below kicks off the doProcessing function but the view dismisses immediately and I can tell from the console that my doProcessing function is still running. This is not right either.
function onButtonClick() {
DispatchQueue.global(qos: .background).async {
print("Processing")
self.doProcessing() // run in background
DispatchQueue.main.async {
self.displaySpinningGear()
}
}
if let viewController = presentingViewController {
// This block will dismiss both current and a view controller presenting current
viewController.dismiss(animated: true, completion: nil)
}
else {
// This block will dismiss only current view controller
self.dismiss(animated: true, completion: nil)
}
}
How do I get the background function to kick off while displaying a spinning gear and dismiss the view and alert controller when the background function is done running (not before)?
EDIT
Tried moving the code to spin the gear outside the background block as per #Honey's suggestion in the comment but to no avail. The view immediately dismisses while the process function is still processing (I can tell through print statements).
func onButtonClick() {
DispatchQueue.main.async {
self.displaySpinningGear()
}
DispatchQueue.global(qos: .background).async {
print("Processing")
self.doProcessing() // run in background
}
if let viewController = presentingViewController {
// This block will dismiss both current and a view controller presenting current
viewController.dismiss(animated: true, completion: nil)
}
else {
// This block will dismiss only current view controller
self.dismiss(animated: true, completion: nil)
}
}
Make a Callback from long running function so when it ends returns a value and catch it to disappear the alert.
Try it:
typealias DoProcessingCallback = (_ finished: Bool) -> Void
func onButtonClick() {
self.displaySpinningGear()
self.doProcessing(callback: { (finished) in
if finished {
// Here you DismissViewController
// Here you DismissAlert
}
}) // long running function
}
func doProcessing(callback: DoProcessingCallback) {
// YOUR LONG CODE....
// When you know it already finished
callback(true)
}
Hope it helps you
I had the same issue and tried a bunch of different things and this is what worked:
activityView.alpha = 0.8
DispatchQueue.global(qos: .default).async(execute: {
DispatchQueue.main.async(execute: {
self.performSegue(withIdentifier: "cropToProcessed", sender: self)
})
})
Basically I set the alpha for activity indicator to 0.0 initially and when the button is pressed I set it to 0.8 and I set it back to 0.0 in viewWillDisappear and it works

Swift : Share image using UIActivityViewController

I want to share QR image on tap of button using ActivityViewController.
Below is code that I’ve used :
#IBAction func btnShareQRCode_Clicked(sender: UIButton) {
self.shareQRCodeUsingActivityViewController(self.imageviewQRCode.image!)
}
func shareQRCodeUsingActivityViewController(imageParamater: UIImage) {
let activityItem: [UIImage] = [imageParamater as UIImage]
let objActivityViewController = UIActivityViewController(activityItems: activityItem as [UIImage], applicationActivities: nil)
objActivityViewController.excludedActivityTypes = [UIActivityTypeAirDrop, UIActivityTypeAddToReadingList]
// objActivityViewController.popoverPresentationController?.sourceView = sender
self.presentViewController(objActivityViewController, animated: true, completion: {
objActivityViewController.completionWithItemsHandler = { activity, success, items, error in
if !success { print("cancelled")
return
}
if activity == UIActivityTypeMail {
print("mail")
}
else if activity == UIActivityTypeMessage {
print("message")
}
else if activity == UIActivityTypeSaveToCameraRoll {
print("camera")
}
}
})
}
func completionHandler() {
}
The issue with this is that it is getting crashed on mail stating an error regarding MailComposer.
I want to know how and where these MailComposer function should be handled?
If you are running this on iOS Simulator, Mail component is likely to fail.
Apart from that, I don't think you need to cast your activity items list as UIImage. Simply put an array of objects as a activityItems array.

Custom UIStoryboardSegue - UIViewController supportedInterfaceOrientations never call

I have a problem creating a custom segue: I'm overriding the "perform()" method as follows
override func perform() {
if !isBack {
self.destinationViewController.view.frame = CGRectMake(self.sourceViewController.view.frame.width, 0, self.sourceViewController.view.frame.width, self.sourceViewController.view.frame.height)
self.sourceViewController.view.addSubview(self.destinationViewController.view)
self.sourceViewController.addChildViewController(self.destinationViewController)
print("Source: \(self.sourceViewController) ---> Destination: \(self.destinationViewController)")
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.destinationViewController.view.frame = CGRectMake(0, 0, self.destinationViewController.view.frame.width, self.destinationViewController.view.frame.height)
}, completion: { (bolean) -> Void in
print("Animation Completed")
})
}else{
print("Source: \(self.sourceViewController) ---> Destination: \(self.destinationViewController)")
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.sourceViewController.view.frame = CGRectMake(self.sourceViewController.view.frame.width, 0, self.sourceViewController.view.frame.width, self.sourceViewController.view.frame.height)
}, completion: { (bolean) -> Void in
print("Animation Completed")
self.sourceViewController.view.removeFromSuperview()
})
}
Everything runs fine but, at this point, the rotation method in "destinationViewController" are not being called. So, if I lock "sourceViewController" to portrait mode while leaving the "destinationViewController" unlocked the latter doesn't rotate.
Assuming the problem resides in this line:
self.sourceViewController.view.addSubview(self.destinationViewController.view)
how should I change the code to fix the rotation?
Ok guy i have find the solution like this.
override func perform() {
if !isBack {
self.destinationViewController.view.frame = CGRectMake(self.sourceViewController.view.frame.width, 0, self.sourceViewController.view.frame.width, self.sourceViewController.view.frame.height)
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(self.destinationViewController.view, aboveSubview: self.sourceViewController.view)
print("Source: \(self.sourceViewController) ---> Destination: \(self.destinationViewController)")
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.destinationViewController.view.frame = CGRectMake(0, 0, self.destinationViewController.view.frame.width, self.destinationViewController.view.frame.height)
}, completion: { (bolean) -> Void in
print("Animazione Terminata")
self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: nil)
})
}else{
print("Indietro Source: \(self.sourceViewController) ---> Destination: \(self.destinationViewController)")
let snapshot = self.sourceViewController.view.snapshotViewAfterScreenUpdates(false)
snapshot.frame = CGRectMake(0, 0, snapshot.frame.size.width, snapshot.frame.size.height)
self.destinationViewController.view.addSubview(snapshot)
self.sourceViewController.dismissViewControllerAnimated(false, completion: nil)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
UIView.animateWithDuration(0.5, animations: { () -> Void in
snapshot.frame = CGRectMake(self.destinationViewController.view.frame.width, 0, self.destinationViewController.view.frame.width, self.destinationViewController.view.frame.height)
}, completion: { (bolean) -> Void in
print("Animazione Terminata")
snapshot.removeFromSuperview()
})
})
}
}
i have understand that the UIStoryboar in the end doing the same operation like a presentation. So before you have to create the animation, then present. And when you need to dismiss before get a snapshot of screen dismiss source VC without animation (add snap in destination VC before dismiss) and in the end animate the snap.
tnx and buy.
sorry for my bad english but talk is chip i show the code.
Edit: There is a litle imprecision in my code. For prevent problem of animation simply set inside the present o dismiss complanion your animation.

UIActivityViewController UIActivityViewControllerCompletionWithItemsHandler

List item
Using Swift for an app that runs in iOS 8, I need to write a completion handler for the UIActivityViewController to capture the results of which "share" method a user selected.
This is a snippet of the code I have so far. My question is how to I set the avc.completionWithItemsHandler? I'm sure it's simple, but I don't see it.
var activityItems = NSMutableArray()
activityItems.addObject("Email or text for 'share' goes here")
var avc = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
avc.setValue("Subject for Email", forKey: "Subject")
avc.completionWithItemsHandler = //Here is where I dont know what to do.
self.navigationController?.presentViewController(avc, animated: true, completion: nil)
The completionWithItemsHandler typealias:
typealias UIActivityViewControllerCompletionWithItemsHandler = (String?, Bool, [AnyObject]?, NSError?) -> Void
Note: the previous code block is not to be used in your project, it just shows the type of closure needed (docs).
So those are the parameters that are passed into the completion handler for you to do with as you will, so the completion handler would look like this:
avc.completionWithItemsHandler = { activity, success, items, error in
}
NOTE: Because I didn't read the "SWIFT" part of the question, I answered the question in Obj-C. My bad, To the OP: I apologize
Here is a more complete answer that actually can be compiled. I used: dispatch_async in order to do an alert so you can see what "activityType" ended up being.
avc.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertViewQuick(#"Activity Status", activityType, #"OK");
});
if (completed)
{
NSLog(#"The Activity: %# was completed", activityType);
}
else
{
NSLog(#"The Activity: %# was NOT completed", activityType);
}
};
As this answer says, for Swift 3 and 4 and iOS 10 and 11 use it like this:
activityVC.completionWithItemsHandler = {(activityType: UIActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in
}
present(activityVC, animated: true, completion: nil)
This was answered quite a while ago, but has a mix of missing and non-swift info so here's my version in the hope that it will help someone needing a more complete example of the completion handler:
avc.completionWithItemsHandler = {[weak self](activityTypeChosen, completed:Bool, returnedItems:[AnyObject]?, error:NSError?) -> Void in
// ReturnedItems is an array of modified NSExtensionItem, or nil of nothing modified
// if (activityType == nil) User dismissed the view controller without making a selection.
// Dismiss the view controller we presented
// (assume a reference to it was stored in self.activityVC)
self?.activityVC?.dismissViewControllerAnimated(true, completion: {
if activityTypeChosen == nil {
NSLog("User canceled without choosing anything")
}
else if completed {
NSLog(")User chose an activity and iOS sent it to that other app/service/whatever OK")
}
else {
NSLog("There was an error: \(error)")
}
})
}
Note the line where it dismisses the view controller. The docs for UIActivityViewController say very explicitly that your app is responsible for both presenting the VC and dismissing it.

Resources