UIActivityIndicatorView doesn't stop at the right time - ios

I'm trying to share video from my app with UIActivityViewController.
So, the process looks like that:
User is tapping on Share button.
I have to prepare video for him with my function saveToShare().
I'm starting animation for my UIActivityIndicatorView and launching saveToShare().
I'm sending notification from saveToShare to my controller.
Observer in my controller is launching function shareVideo().
shareVideo() looks like that:
func videoIsReady() {
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
let videoName = "NewWatermarkedVideoNew2Share.mov"
let exportPath = NSTemporaryDirectory().appending(videoName)
let exportUrl = URL(fileURLWithPath: exportPath)
let urlData = NSData(contentsOf: exportUrl)
if ((urlData) != nil){
let videoLink = exportUrl
let objectsToShare = [videoLink]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = self.view // so that iPads won't crash
activityVC.setValue("#myhashtag", forKey: "subject")
activityVC.excludedActivityTypes = [UIActivityType.airDrop, UIActivityType.addToReadingList, UIActivityType.assignToContact, UIActivityType.copyToPasteboard, UIActivityType.openInIBooks, UIActivityType.postToTencentWeibo, UIActivityType.postToVimeo, UIActivityType.postToWeibo, UIActivityType.print, UIActivityType.saveToCameraRoll, UIActivityType.postToFlickr, UIActivityType.postToTwitter, UIActivityType(rawValue: "com.apple.reminders.RemindersEditorExtension"), UIActivityType(rawValue: "com.apple.mobilenotes.SharingExtension"),UIActivityType(rawValue: "com.google.Drive.ShareExtension"), UIActivityType(rawValue: "com.apple.mobileslideshow.StreamShareService")]
self.present(activityVC, animated: true, completion: {
})
} else {
print("url is empty...")
}
}
It works, but my UIActivityIndicatorView is not hidden before share dialog and actually is working for several seconds after that dialog is shown.
What is wrong here?
P.S. So, it works if I put UIActivityIndicatorView in DispatchQueue.main.async so my problem is solved but I don't know why this problem was arisen in the first place.

To get it to disappear immediately, you'll want to call the code on the main queue synchronously. To avoid a dead lock (in case videoIsReady() is called from the main queue) use this little extension I've developed:
extension DispatchQueue {
class func safeUISync(execute workItem: DispatchWorkItem) {
if Thread.isMainThread { workItem.perform() }
else { DispatchQueue.main.sync(execute: workItem) }
}
class func safeUISync<T>(execute work: () throws -> T) rethrows -> T {
if Thread.isMainThread { return try work() }
else { return try DispatchQueue.main.sync(execute: work) }
}
}
Now you can call your code as follows:
func videoIsReady() {
DispatchQueue.safeUISync {
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
}
...
}

You must be calling your videoIsReady() function from a background thread. All UI calls must be made from the main thread or the results are undefined. A common result is for the UI changes to take a LOOONG time to appear. (Another common result is a crash.)

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)

Sharing an AVPlayerAsset with UIActivityViewController

I'd like to share an Audio file that I have loaded into a AVPlayerAsset. I was wondering if this is possible, or what the correct way of doing this would look like. Here is what my code looks like:
func shareTrack(track: Track) {
guard let file = track.playerItem else { return } // This is the AVPlayerAsset
let activityController = UIActivityViewController(activityItems: [file], applicationActivities: nil)
activityController.completionWithItemsHandler = { (nil, completed, _, error ) in
if completed {
print("Success")
} else {
print("Canceled")
}
}
DispatchQueue.main.async{
self.present(activityController, animated: true)
}
}
You are using the word "file" but the asset is not a file. It is an asset. Other programs aren't going to know anything about that. If you want to share something, share a file URL.

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.

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 completion handler still calls action if user presses cancel

In my UIActivityViewController, I use completion handler to execute a "successfully shared" notification. It works but my only problem is, it still shows the notification if the user presses cancel.
Here is my completion handler code,
[controller setCompletionHandler:^(NSString *activityType, BOOL completed) {
CWStatusBarNotification *notification = [CWStatusBarNotification new];
[notification displayNotificationWithMessage:#"✓ Successfully Shared Centre!"
forDuration:3.0f];
notification.notificationLabelBackgroundColor = [UIColor colorWithRed:38.0f/255.0f green:81.0f/255.0f blue:123.0f/255.0f alpha:1.0f];
notification.notificationLabelTextColor = [UIColor whiteColor];
}];
Thanks for the help!
Note: the completionHandler property is deprecated in iOS8, so it's not possible anymore to know the result of a share action.
https://developer.apple.com/documentation/uikit/uiactivityviewcontroller/1622010-completionhandler
Update:
Like adruzh said, on iOS8 there's a new completionHandler that Apple forgot to mention in the documentation:
[activityController setCompletionWithItemsHandler:
^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
}];
https://developer.apple.com/documentation/uikit/uiactivityviewcontroller/1622022-completionwithitemshandler
That's what the completed argument is for:
[controller setCompletionHandler:^(NSString *activityType, BOOL completed) {
if (!completed) return;
CWStatusBarNotification *notification = [CWStatusBarNotification new];
[notification displayNotificationWithMessage:#"✓ Successfully Shared Centre!"
forDuration:3.0f];
notification.notificationLabelBackgroundColor = [UIColor colorWithRed:38.0f/255.0f green:81.0f/255.0f blue:123.0f/255.0f alpha:1.0f];
notification.notificationLabelTextColor = [UIColor whiteColor];
}];
For Swift, this is what worked for us:
...
// Configure UIActivityViewController
let activityViewController = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityViewController.excludedActivityTypes = [UIActivityTypeAirDrop,
UIActivityTypeAddToReadingList,
UIActivityTypeAssignToContact,
UIActivityTypePrint,
UIActivityTypeCopyToPasteboard]
// Show UIActivityViewController
presentViewController(activityViewController, animated: true, completion: nil)
// Define completion handler
activityViewController.completionWithItemsHandler = doneSharingHandler
...
func doneSharingHandler(activityType: String!, completed: Bool, returnedItems: [AnyObject]!, error: NSError!) {
// Return if cancelled
if (!completed) {
return
}
// If here, log which activity occurred
println("Shared video activity: \(activityType)")
}
Swift 5 - The below function covers most of the UIActivityViewController properties. It worked for me and thought so it might be helpful for you guys as well.
func performShareAction() {
let itemsToShare : [Any] = ["Hello World"]
let activityView = UIActivityViewController(activityItems: itemsToShare, applicationActivities: nil)
// Apps that you want to exclude sharing the items
let excludedActivityTypes : [UIActivity.ActivityType] = [
.addToReadingList,
.assignToContact,
.copyToPasteboard,
.mail,
.markupAsPDF,
.message,
.openInIBooks,
.postToFacebook,
.postToFlickr,
.postToTencentWeibo,
.postToTwitter,
.postToVimeo,
.postToWeibo,
.print,
.saveToCameraRoll
]
activityView.excludedActivityTypes = excludedActivityTypes
self.present(activityView, animated: true, completion: nil)
activityView.completionWithItemsHandler = { activityType, completed, items, error in
// Event Cancelled
if !completed {
print("Content Sharing was cancelled.")
return
}
// Content Shared on particular activity
print("Shared on activity type: \(String(describing: activityType?.rawValue))")
// Detect app on which the items are shared
if let type = activityType {
switch type {
case .addToReadingList: print("Added To Reading List"); break
case .airDrop: print("AirDropped to Other Device"); break
case .assignToContact: print("Assigned To Contact"); break
case .copyToPasteboard: print("Copied To Pasteboard"); break
case .mail: print("Mailed"); break
case .markupAsPDF: print("Marked-Up As PDF"); break
case .message: print("Messaged"); break
case .openInIBooks: print("Opened In iBooks"); break
case .postToFacebook: print("Posted To Facebook"); break
case .postToFlickr: print("Posted To Flickr"); break
case .postToTencentWeibo: print("Posted To Tencent Weibo"); break
case .postToTwitter: print("Posted To Twitter"); break
case .postToVimeo: print("Posted To Vimeo"); break
case .postToWeibo: print("Posted To Weibo"); break
case .print: print("Printed"); break
case .saveToCameraRoll: print("Saved To Camera Roll"); break
default: print("Shared with new app"); break
}
}
}
}
For the Swifties out there, here's how you would code this in Swift along with some share service detection:
activityViewController.completionHandler = {(activityType, completed:Bool) in
if !completed {
//cancelled
return
}
//shared successfully
//below is how you would detect for different sharing services
var activity:String = "other"
if activityType == UIActivityTypePostToTwitter {
activity = "twitter"
}
if activityType == UIActivityTypeMail {
activity = "mail"
}
//more code here if you like
}
The completed parameter will be NO is the user cancels.
[controller setCompletionHandler:^(NSString *activityType, BOOL completed) {
if (completed) {
CWStatusBarNotification *notification = [CWStatusBarNotification new];
[notification displayNotificationWithMessage:#"✓ Successfully Shared Centre!"
forDuration:3.0f];
notification.notificationLabelBackgroundColor = [UIColor colorWithRed:38.0f/255.0f green:81.0f/255.0f blue:123.0f/255.0f alpha:1.0f];
notification.notificationLabelTextColor = [UIColor whiteColor];
}
}];
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
//do some action
}
see my answer here: https://stackoverflow.com/a/34581940/1109892
Swift 3
func completionHandler(activityType: UIActivityType?, shared: Bool, items: [Any]?, error: Error?) {
if (shared) {
print("Cool user shared some stuff")
}
else {
print("Bad user canceled sharing :(")
}
}
activityController.completionWithItemsHandler = completionHandler

Resources