UIActivityViewController CompletionBlock Issue. Does not get called when user "Cancels" message - ios

I have a bug I've been stuck on for a bit and was wondering if someone can help.
My UIActivityViewController is called within a navigationController.
The issue is the completionblock is only called SOMETIMES.
Specifically for Messages and Mail
The only instance when it actually calls the completionBlock is when user sends a successful message/mail.
If user cancels, it does not call the completionBlock which causes it to hang: Though for Mail you can dismiss the modal, for messages you cannot.
Thank you!
let shareContent = ["Write your share content"]
let activityController = UIActivityViewController(activityItems: shareContent,
applicationActivities: nil)
self.present(activityController, animated: true, completion: nil)
//Completion handler
activityController.completionWithItemsHandler = { (activityType: UIActivity.ActivityType?, completed:
Bool, arrayReturnedItems: [Any]?, error: Error?) in
if completed {
print("share completed")
return
} else {
print("cancel")
}
if let shareError = error {
print("error while sharing: \(shareError.localizedDescription)")
}
}

So after a bit, I've determined that the issue is highly likely a conflicting cocoapod or another library.
To combat this issue, we resolved this by creating a Custom Activity that mimics the "look" of messaging but just calls the MFMessageViewController regularly. With the delegate attached to the custom activity itself.

Related

Is there anyway to know when the Share Model of the iOS will be presented or dismissed?

I would like to know if iOS provides us any way to know when the Share Modal will be presented or dismissed.
var completionWithItemsHandler: UIActivityViewController.CompletionWithItemsHandler? { get set }
The completion handler to execute after the activity view controller is dismissed.
let activity = UIActivityViewController(activityItems: [activityItems], applicationActivities: nil)
activity.completionWithItemsHandler = {(activityType: UIActivity.ActivityType?, completed: Bool, returnedItems:[Any]?, error: Error?) in
// dismiss activity
}
self.present(activity, animated: true, completion: nil)
Presents a view controller modally.
completion
self.present(activity, animated: true) {
}
The block to execute after the presentation finishes. This block has no return value and takes no parameters. You may specify nil for this parameter.

UIView.addSubView() not working within a closure within a closure

Essentially I'm trying to creating a function that sets up multiple notifications and while it does this, I want it to display a loading/progress bar in an alert view and update this while it sets up the notifications.
The problem is that the UI is not updating when I'm adding the progress view to the alert view as a subview, until the processing is complete.
Usually I fix this kind of problem with 'DispatchQueue.main.async'. But this doesn't seem to make a difference.
Below is the code. In the 'PresentLoadingView' function, the processing in the 'SetUpNotifications' function that is passed as the 'viewPresented' closure function finishes before the UI actually shows that the 'progressView' is added to the 'loadingView'.
I'm pretty sure this is a threading issue as it doesn't seem to reach the main UI thread when using 'DispatchQueue.main.async', possibly something to do with it having an #escaping parameter? Or just the fact that it is a closure within a closure?
var centre: UNUserNotificationCenter?
func AttemptToSetUpNotifications() {
centre = UNUserNotificationCenter.current()
centre!.requestAuthorization(options: [.alert, .badge], completionHandler: SetUpNotificationsChecks)
}
func SetUpNotificationsChecks(granted: Bool, error: Error?) {
if error != nil {
return
}
if !granted {
return
}
PresentLoadingView(viewPresented: SetUpNotifications)
}
var loadingView: UIAlertController?
func PresentLoadingView(viewPresented: #escaping ((UIProgressView)-> Void)) {
loadingView = UIAlertController(title: "Setup Notifications", message: "Setting up notifications, please wait...", preferredStyle: .alert)
self.present(loadingView!, animated: true, completion: {
// Add your progressbar after alert is shown
let margin:CGFloat = 8.0
let rect = CGRect(x:margin, y:72.0, width:self.loadingView!.view.frame.width - margin * 2.0 , height:2.0)
let progressView = UIProgressView(frame: rect)
progressView.progress = 0.0
progressView.tintColor = Common.CommonTintColor
self.loadingView!.view.addSubview(progressView)
// DispatchQueue.main.async {
// self.loadingView!.view.addSubview(progressView)
// }
viewPresented(progressView)
})
}
Any help would be greatly appreciated, sorry if the problem is my lack of concurrency knowledge, and if you need anymore info, please ask.
Thanks.

App store Rejected me from the app crashing but it has never crashed for me through numerous testing

I submitted my app to the app store, but they rejected it saying that the app crashed when they clicked the feedback button which is a button that opens up a MFMailComposeViewController. The problem I am having is I have run it on many devices between the simulator and actual devices, yet I have never had this problem. I will post my functions for the feedback button below which I have called and are all connected to the button (Like I said it works completely fine every time I have tested it), and my question is: Am i doing something wrong in the code to where only they get the crash?
func giveFeedback()
{
let email = ["info#website.com"]
var fvc = view?.window?.rootViewController
var cev = MFMailComposeViewController()
cev.mailComposeDelegate = self
cev.setToRecipients(email)
cev.setSubject("MyApp")
fvc?.presentViewController(cev, animated: true, completion: nil)
}
func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!)
{
controller.dismissViewControllerAnimated(true, completion: nil)
}
Also, I have imported MessageUI, and in the class I have my MFMailComposeViewControllerDelegate
One thing is that you don't call canSendMail. I believe that if you try to show the MFMailComposeViewController when mails are disabled, your app would crash.
In your function you would use it for example like this:
func giveFeedback(contextViewController: UIViewController) {
if MFMailComposeViewController.canSendMail() {
let email = ["info#website.com"]
var cev = MFMailComposeViewController()
cev.mailComposeDelegate = self
cev.setToRecipients(email)
cev.setSubject("MyApp")
contextViewController.presentViewController(cev, animated: true, completion: nil)
}
}
But it would be best to check the status earlier and display the button only if email is enabled on the device...
Well I will have to guess too.... There are two things that look suspicious to me... the first thing is the line
var fvc = view?.window?.rootViewController
could you change it this way?
func giveFeedback(contextViewController: UIViewController) {
let email = ["info#website.com"]
var cev = MFMailComposeViewController()
cev.mailComposeDelegate = self
cev.setToRecipients(email)
cev.setSubject("MyApp")
contextViewController.presentViewController(cev, animated: true, completion: nil)
}

UIActivityViewController completion handler returns success when tweet has failed

I'm using a UIActivityViewController to display a share sheet so users can share my app. I'm currently testing tweets and i'm getting some unexpected results. On tweeting for the first time, all goes well. On the second time, i'm getting a duplicate tweet error message, which is expected. The problem is that the completionWithItemsHandler is returning success: Bool as true!
I want to be able to display my own personalised message to the user rather than the massive one that is returned currently.
Here is my code:
#IBAction func ShareButtonTapped(sender: AnyObject) {
let textToShare = "I'm using Buzz! The new way to send emoji's, with sound, it's annoying, funny and amazing"
var url = NSURL(string: "-Image url masked out-")
var data = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
if let myWebsite = NSURL(string: "-redirect masked out-")
{
let objectsToShare = [textToShare, myWebsite]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.completionWithItemsHandler = {
(activity, success, items, error) in
println("Activity: \(activity) Success: \(success) Items: \(items) Error: \(error)")
}
self.presentViewController(activityVC, animated: true, completion: { () -> Void in
})
}
}
Here is my log:
2015-01-27 11:10:58.021 Buzz[3239:813859] LaunchServices:
invalidationHandler called
2015-01-27 11:10:58.052 Buzz[3239:813860]
LaunchServices: invalidationHandler called Activity:
com.apple.UIKit.activity.PostToTwitter Success: true Items: nil Error: nil
2015-01-27 11:11:04.134 Buzz[3239:813859] LaunchServices:
invalidationHandler called
2015-01-27 11:11:09.182 Buzz[3239:813859] plugin com.apple.share.Twitter.post invalidated
Use completion handler like this For SWIFT 3 AND 4, iOS 10 AND 11 :
activityVC.completionWithItemsHandler = {(activityType: UIActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in
if !completed {
// User canceled
return
}
// User completed activity
}
self.present(activityVC, animated: true, completion: nil)
SWIFT 2.0 iOS 8.0 >, you should use completion handler like this:
self.presentViewController(activityVC, animated: true, completion: nil)
activityVC.completionWithItemsHandler = {(activityType, completed:Bool, returnedItems:[AnyObject]?, error: NSError?) in
// Return if cancelled
if (!completed) {
return
}
//activity complete
//some code here
}
I don't think you can affect the feedback flow of the UIActivityViewController as it is high-level, easy-to-use component that is not tailored for fine-grained customization.
What you can do, though, is to save the state that user has tweeted this exact message after the first tweet and then disable Twitter from UIActivityController using excludedActivityTypes and UIActivityTypePostToTwitter. So, instead of showing an error for a duplicate tweet, you prevent the action sequence even from happening.
I suggest replacing
self.presentViewController(activityVC, animated: true, completion: { () -> Void in })
with
self.presentViewController(activityVC, animated: true, completion: nil)
This worked for me. Hope it helps!

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