Custom message fetched from API in UIActivityViewController - ios

I want my app users to share an link using an UIACtivityViewController. The link is not static and I have to fetch it from an api. I want to fetch this link as soon as someone presses a icon in UIActivityViewController but how I am doing it now is not working. The completionhandler of the request is always excuted at the end of the method fetchShareURL(). Could someone tell me how to fix this?
import UIKit
class ShareItemSource: NSObject, UIActivityItemSource {
var objects : [AnyObject]?
var shareURL : String?
init(objects : [AnyObject]?){
self.objects = objects
}
func fetchShareURL() -> Bool{
//fetch share url from api
let api = Api()
let semaphore = dispatch_semaphore_create(0);
let backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)
dispatch_async(backgroundQueue, {
api.export(self.groupObjList, receiptObjList: self.receiptObjList){ status, message, url in
dispatch_async(dispatch_get_main_queue(), {
self.shareURL = url
dispatch_semaphore_signal(semaphore);
})
}
});
let delayInSeconds = 30.0;
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
dispatch_semaphore_wait(semaphore, delayTime)
if self.shareURL != nil{
return true
} else {
return false
}
}
#objc func activityViewControllerPlaceholderItem(activityViewController: UIActivityViewController) -> AnyObject {
return ""
}
#objc func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? {
if fetchShareURL() == true{
return "Download files: "+self.shareURL!
} else {
return nil
}
}
func activityViewController(activityViewController: UIActivityViewController, subjectForActivityType activityType: String?) -> String {
return "title of share"
}
func activityViewController(activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: String?, suggestedSize size: CGSize) -> UIImage? {
//do stuff
}
}

If data to be provided in a UIActivityView takes time to provide, use a UIActivityItemProvider. It is an NSOperation subclass and solves the whole problem in a completely coherent way. Indeed, this sort of situation is exactly what it is intended for. (And watch the WWDC 2015 video on advanced NSOperation, please.)

Changed it to this for now. Later I'll use an NSOperation.
api.export(self.objects){ status, message, url in
if(status == true){
if let url = url{
alert.dismissViewControllerAnimated(true){
let sharingItems = ["Share url", url]
let activityViewController = UIActivityViewController(activityItems: [ShareItemSource(url: url)], applicationActivities: nil)
activityViewController.excludedActivityTypes = [UIActivityTypeAddToReadingList, UIActivityTypeCopyToPasteboard]
self.presentViewController(activityViewController, animated: true, completion: nil)
activityViewController.completionWithItemsHandler = { (activity, success, items, error) in
//Response
}
}
}
} else {
//handle fail
}
}

Related

Open in Safari with UIActivityViewController?

I'm sharing a URL via UIActivityViewController. I'd like to see "Open in Safari" or "Open in browser" appear on the share sheet, but it doesn't. Is there a way to make this happen?
Note: I am not interested in solutions that involve adding somebody else's library to my app. I want to understand how to do this, not just get it to happen. Thanks.
Frank
Yes, you could add your custom action to Share sheet in iOS
You would have to copy this class.
class MyActivity: UIActivity {
var _activityTitle: String
var _activityImage: UIImage?
var activityItems = [Any]()
var action: ([Any]) -> Void
init(title: String, image: UIImage?, performAction: #escaping ([Any]) -> Void) {
_activityTitle = title
_activityImage = image
action = performAction
super.init()
}
override var activityTitle: String? {
return _activityTitle
}
override var activityImage: UIImage? {
return _activityImage
}
override var activityType: UIActivity.ActivityType {
return UIActivity.ActivityType(rawValue: "com.someUnique.identifier")
}
override class var activityCategory: UIActivity.Category {
return .action
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
return true
}
override func prepare(withActivityItems activityItems: [Any]) {
self.activityItems = activityItems
}
override func perform() {
action(activityItems)
activityDidFinish(true)
}
}
Please go through the class you might need to change a few things.
This is how you use it.
let customItem = MyActivity(title: "Open in Safari", image: UIImage(systemName: "safari") ) { sharedItems in
guard let url = sharedItems[0] as? URL else { return }
UIApplication.shared.open(url)
}
let items = [URL(string: "https://www.apple.com")!]
let ac = UIActivityViewController(activityItems: items, applicationActivities: [customItem])
ac.excludedActivityTypes = [.postToFacebook]
present(ac, animated: true)
I have done this for one action, and tested it, it works.
Similarly you could do it for other custom actions.
For more on it refer this link.
Link To Detailed Post

Returning value while dispatching to the main thread

I have a function func getValue() -> Bool that's called from a background thread. This is intentional and also required. Now, the getValue() needs to execute something on the main thread, in this case it needs to access UIApplication.shared.canOpenURL, which must be run on the main queue.
This is my current function:
func getValue() -> Bool {
guard let url = URL(string: "someurl") else { return false }
return UIApplication.shared.canOpenURL(url)
}
How can I convert that function to a thread safe one, namely to make sure it always runs on the main thread, without
calling the function from the main thread to begin with
refactoring the function to return the value in a closure
I've tried this:
// This causes a deadlock, see https://stackoverflow.com/a/42484670/1531270
func getValue() -> Bool {
var flag = false
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
if let url = URL(string: "someurl"), UIApplication.shared.canOpenURL(url) {
flag = true
}
group.leave()
}
group.wait()
return flag
}
and this:
// This crashes with EXC_BREAKPOINT (SIGTRAP) dispatch_sync called on queue already owned by current thread
func getValue() -> Bool {
return DispatchQueue.main.sync {
guard let url = URL(string: "someurl") else { return false }
return UIApplication.shared.canOpenURL(url)
}
}
but neither of them works. Any ideas?
You're looking for a semaphore - try this:
DispatchQueue.global(qos: .background).async {
var value: Bool? = nil
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.main.async {
let alert = UIAlertController(title: "Choose one", message: "Take your time, I'll wait", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "true", style: .default, handler: { _ in
value = true
semaphore.signal()
}))
alert.addAction(UIAlertAction(title: "false", style: .default, handler: { _ in
value = false
semaphore.signal()
}))
self.present(alert, animated: true, completion: nil)
}
semaphore.wait()
print("Choice: \(value!)")
}
Or to use your example from above:
func getValue() -> Bool {
var flag = false
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.main.async {
if let url = URL(string: "someurl"), UIApplication.shared.canOpenURL(url) {
flag = true
semaphore.signal()
}
}
semaphore.wait()
return flag
}
I can't reproduce any issue with your second example. You didn't show how you're calling getValue, so I made something up:
func getValue() -> Bool {
return DispatchQueue.main.sync {
guard let url = URL(string: "testing://testing") else { return false }
return UIApplication.shared.canOpenURL(url)
}
}
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global(qos:.background).async {
let ok = self.getValue()
print(ok) // false, the right answer
}
}
There's no "crash", so I would just go with that. When I use the scheme testing: I get false, and when I change testing: to https:, I return true, so clearly the method call is working.

Swift UIActivityViewController

Could anyone tell me how to implement "Open in Safari" in UIActivityViewController? I know this questions is a duplicate of another question posted a long time ago, and the method at that time was by using a framework that can no longer be used.
The data I am sharing is a URL. I already have a fully working ActivityVC and I only need to add that “open in safari” button.
Thank you very much.
code:
#IBAction func shareButtonPressed(_ sender: UIButton) {
let activityVC = UIActivityViewController(activityItems: [URL(string: urlStr)!], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = self.view
self.present(activityVC, animated: true, completion: nil)
}
You need to implement your own activity, please check the code below.
import UIKit
final class SafariActivity: UIActivity {
var url: URL?
override var activityImage: UIImage? {
return UIImage(named: "SafariActivity")!
}
override var activityTitle: String? {
return NSLocalizedString("Open in Safari", comment:"")
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
for item in activityItems {
if
let url = item as? URL,
UIApplication.shared.canOpenURL(url)
{
return true
}
}
return false
}
override func prepare(withActivityItems activityItems: [Any]) {
for item in activityItems {
if
let url = item as? URL,
UIApplication.shared.canOpenURL(url)
{
self.url = url
}
}
}
override func perform() {
var completed = false
if let url = self.url {
completed = UIApplication.shared.openURL(url)
}
activityDidFinish(completed)
}
}
let url = URL(string: "http://www.apple.com")!
let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: [SafariActivity()])
present(activityViewController, animated: true, completion: nil)
Updated to Swift 5.1 & iOS 13
Bonus:
ActivityType extension to use with .excludedActivityTypes.
UIImage(systemName:) to use SF Symbols plus .applyingSymbolConfiguration to take advantage of its flexibility.
To improve:
Implement completion handler on UIApplication.shared.open to handle errors (unlikely to occur).
import UIKit
extension UIActivity.ActivityType {
static let openInSafari = UIActivity.ActivityType(rawValue: "openInSafari")
}
final class SafariActivity: UIActivity {
var url: URL?
var activityCategory: UIActivity.Category = .action
override var activityType: UIActivity.ActivityType {
.openInSafari
}
override var activityTitle: String? {
"Open in Safari"
}
override var activityImage: UIImage? {
UIImage(systemName: "safari")?.applyingSymbolConfiguration(.init(scale: .large))
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
activityItems.contains { $0 is URL ? UIApplication.shared.canOpenURL($0 as! URL) : false }
}
override func prepare(withActivityItems activityItems: [Any]) {
url = activityItems.first { $0 is URL ? UIApplication.shared.canOpenURL($0 as! URL) : false } as? URL
}
override func perform() {
if let url = url {
UIApplication.shared.open(url)
}
self.activityDidFinish(true)
}
}
Try this Link if it meets your requirement
Link - https://bjartes.wordpress.com/2015/02/19/creating-custom-share-actions-in-ios-with-swift/
Code Required
class FavoriteActivity: UIActivity {
override func activityType() -> String? {
return "TestActionss.Favorite"
}
override func activityTitle() -> String? {
return "Add to Favorites"
}
override func canPerformWithActivityItems(activityItems: [AnyObject]) -> Bool {
NSLog("%#", __FUNCTION__)
return true
}
override func prepareWithActivityItems(activityItems: [AnyObject]) {
NSLog("%#", __FUNCTION__)
}
override func activityViewController() -> UIViewController? {
NSLog("%#", __FUNCTION__)
return nil
}
override func performActivity() {
// Todo: handle action:
NSLog("%#", __FUNCTION__)
self.activityDidFinish(true)
}
override func activityImage() -> UIImage? {
return UIImage(named: "favorites_action")
}
}
Usage
#IBAction func showAvc(sender: UIButton) {
let textToShare = "Look at this awesome website!"
let myWebsite = NSURL(string: "http://www.google.com/")!
let objectsToShare = [textToShare, myWebsite]
let applicationActivities = [FavoriteActivity()]
let avc = UIActivityViewController(activityItems: objectsToShare, applicationActivities: applicationActivities)
self.presentViewController(avc, animated: true, completion: nil)
}

Stripe - retrieveCustomer callback infinite loading

I had implement the Stripe to my project.I'm using an extension of default STPPaymentMethodsViewController like this:
class PaymentMethodVC: STPPaymentMethodsViewController {
convenience init()
{
let theme = STPTheme()
theme.primaryBackgroundColor = UIColor.pintHubDarkBrown
theme.secondaryBackgroundColor = UIColor.pintHubHeaderColor
theme.accentColor = UIColor.white
theme.primaryForegroundColor = UIColor.pintHubOrange
theme.secondaryForegroundColor = UIColor.pintHubOrange
theme.font = UIFont.mainRegular()
let paymentdelegate = PaymentMethodVCDelegate()
let paymentConfig = STPPaymentConfiguration.shared()
paymentConfig.publishableKey = "stripePublickToken"
let apiAdapter = PaymentApiAdapter()
self.init(configuration: paymentConfig, theme: theme, apiAdapter: apiAdapter, delegate: paymentdelegate)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
PaymentMethodVCDelegate is an object that implements STPPaymentMethodsViewControllerDelegate that methods are never called and
PaymentApiAdapter is other object that implements STPBackendAPIAdapter protocol which methods are:
public func retrieveCustomer(_ completion: #escaping Stripe.STPCustomerCompletionBlock)
public func attachSource(toCustomer source: STPSource, completion: #escaping Stripe.STPErrorBlock)
public func selectDefaultCustomerSource(_ source: STPSource, completion: #escaping Stripe.STPErrorBlock)
everything works fine expect when i want to return an error to the callback method func retrieveCustomer(_ completion: #escaping Stripe.STPCustomerCompletionBlock) that is a method of the STPBackendAPIAdapter protocol more details here.
this is my code:
func retrieveCustomer(_ completion: #escaping (STPCustomer?, Error?) -> Swift.Void)
{
stripeEndpoint.getStripeCustomer(for: "myStrypeCustomerId") { (status, JSON) in
if !status.success()
{
let userInfo = [NSLocalizedDescriptionKey:status.error,
NSLocalizedFailureReasonErrorKey: status.code,
NSLocalizedRecoverySuggestionErrorKey: ""
] as [String : Any]
let error = NSError(domain: "MyDomain", code: Int(status.error) ?? 0, userInfo: userInfo)
completion(nil, error)
}
else
{
var customer:STPCustomer? = nil
if let jsonData = JSON
{
let deserializer = STPCustomerDeserializer(jsonResponse: jsonData)
customer = deserializer.customer!
}
completion(customer, nil)
}
}
and when i receive an error the screen displays and infinite loading indicator.
and if i call completion(nil, nil) the loading disappear but i when i press cancel the ViewController don't pop from stack

Give thumbnail image with UIActivityViewController

I'm trying to share an image with text through UIActivityViewController. If I do this:
let activityVC = UIActivityViewController(activityItems: [text, image], applicationActivities: nil)
self.presentViewController(activityVC, animated: true, completion: nil)
Everything works fine. The problem is that I only want to share the image with certain activity types. i.e. when a user shares to Facebook I don't want to have an image, for everything else I do though. My problem is this stupid method is never called:
optional func activityViewController(_ activityViewController: UIActivityViewController,
thumbnailImageForActivityType activityType: String?,
suggestedSize size: CGSize) -> UIImage?
Which should be becuase it's defined in UIActivityItemSource protocol. Is there any work around to this?
So I believe to have made some headway here. Turns our if you pass multiple values of self when instantiating UIActivityViewController you can return multiple values in the itemForActivityType delegate method. So if I do this:
let activityVC = UIActivityViewController(activityItems: [self, self], applicationActivities: nil)
I can return different values like this:
func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? {
if activityType == UIActivityTypePostToFacebook {
return ["hello", "world"]
}
else {
return ["goodnight", "moon"]
}
}
However, it seems that you can only return two values of the same type.
My new question is now, how would I return both an image and text?? The hunt continues...
In order to share two different set of content you have to create two different itemsource
we can set different text content for different activity type.Add the MyStringItemSource class to your viewcontroller
SourceOne:
class MyStringItemSource: NSObject, UIActivityItemSource {
#objc func activityViewControllerPlaceholderItem(activityViewController: UIActivityViewController) -> AnyObject {
return ""
}
#objc func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? {
//You can pass different text for for diffrent activity type
if activityType == UIActivityTypePostToFacebook {
return "String for facebook"
}else{
return "String for Other"
}
}
}
Our requirement is to add image to all activity type except FB,to do that add the MyImageItemSource class in your VC.
SourceTwo:
class MyImageItemSource: NSObject, UIActivityItemSource {
#objc func activityViewControllerPlaceholderItem(activityViewController: UIActivityViewController) -> AnyObject {
return ""
}
#objc func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? {
//This one allows us to share image ecxept UIActivityTypePostToFacebook
if activityType == UIActivityTypePostToFacebook {
return nil
}
let Image: UIImage = UIImage(data: NSData(contentsOfURL: NSURL(string: "https://pbs.twimg.com/profile_images/604644048/sign051.gif")!)!)!
return Image
}
}
Now we are ready to set UIActivityViewController,here we go
#IBAction func Test(sender: AnyObject) {
let activityVC = UIActivityViewController(activityItems: [MyStringItemSource(),MyImageItemSource()] as [AnyObject], applicationActivities: nil)
//Instead of using rootviewcontroller go with your own way.
if let window = (UIApplication.sharedApplication().delegate as? AppDelegate)?.window
{
window.rootViewController?.presentViewController(activityVC, animated: true, completion: nil)
}
}
TWITTER Shared Dialogue:
Contains image and given text
FB Share Dialogue:
Contains only the given text

Resources