Open in Safari with UIActivityViewController? - ios

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

Related

Custom UIActivityViewController not appear image when it is in the "More" menu item

I created custom UIActivity and override variable activityImage used my image.
fileprivate extension UIActivity.ActivityType {
static let extendedMessage =
UIActivity.ActivityType("ExtendedMessage")
}
Custom UIActivity:
fileprivate class ExtendedMessageActivity: UIActivity {
private let phoneNumbers: [String]
private var message: String?
init(phoneNumbers: [String]) {
self.phoneNumbers = phoneNumbers
super.init()
}
override static var activityCategory: UIActivity.Category {
return .share
}
override var activityType: UIActivity.ActivityType? { return .extendedMessage }
override var activityTitle: String? { return NSLocalizedString("Message", comment: "") }
override var activityImage: UIImage? {
return UIImage(named: "message-app-icon")
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
....
}
override func prepare(withActivityItems activityItems: [Any]) {
....
}
override func perform() {
....
}
}
The image appears in the set.
I have a problem the image does not appear when it is in the "More" menu item.
Why the image does not appear?
Had tried to find documentation for the activity settings icon. Not found proper documentation. Just add the below code in your custom UIActivity, Working fine for me with Swift 5.2
#objc var _activitySettingsImage: UIImage? {
return UIImage(named: "activitySettingsIcon")
}
Don't forgot to activitySettingsIcon image set in 29pt.
I solved the same issue by adding the following (in Objective C, but you get the idea).
- (UIImage *)_activitySettingsImage {
return [UIImage imageNamed:#"message-app-icon-for-setting"];
}
Apparently, image for more section should be smaller than the one in _activityImage. At the time of writing (XCode 12.3), 60pt for activityImage and 30pt for activitySettingsImage work for me. (I also added #2x for each image)..

Hide or disable share button from uidocumentinteractioncontroller in swift 5

In my application, I'm using the QuickLook framework to view the document files such as pdf, ppt, doc, etc. etc. But due to privacy concerns, I don't want that the user can share this document with others so please let me know how to disable/hide the share button and also the copy-paste option.
I know this question can be asked by a number of times and tried many solutions but nothing works for me
hide share button from QLPreviewController
UIDocumentInteractionController remove Actions Menu
How to hide share button in QLPreviewController using swift?
Hide right button n QLPreviewController?
Please suggest to me to achieve this.
Here is my demo code:
import UIKit
import QuickLook
class ViewController: UIViewController {
lazy var previewItem = NSURL()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func displayLocalFile(_ sender: UIButton){
let previewController = QLPreviewController()
// Set the preview item to display
self.previewItem = self.getPreviewItem(withName: "samplePDf.pdf")
previewController.dataSource = self
self.present(previewController, animated: true, completion: nil)
}
#IBAction func displayFileFromUrl(_ sender: UIButton){
// Download file
self.downloadfile(completion: {(success, fileLocationURL) in
if success {
// Set the preview item to display======
self.previewItem = fileLocationURL! as NSURL
// Display file
let previewController = QLPreviewController()
previewController.dataSource = self
self.present(previewController, animated: true, completion: nil)
}else{
debugPrint("File can't be downloaded")
}
})
}
func getPreviewItem(withName name: String) -> NSURL{
// Code to diplay file from the app bundle
let file = name.components(separatedBy: ".")
let path = Bundle.main.path(forResource: file.first!, ofType: file.last!)
let url = NSURL(fileURLWithPath: path!)
return url
}
func downloadfile(completion: #escaping (_ success: Bool,_ fileLocation: URL?) -> Void){
let itemUrl = URL(string: "https://images.apple.com/environment/pdf/Apple_Environmental_Responsibility_Report_2017.pdf")
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent("filename.pdf")
// to check if it exists before downloading it
if FileManager.default.fileExists(atPath: destinationUrl.path) {
debugPrint("The file already exists at path")
completion(true, destinationUrl)
// if the file doesn't exist
} else {
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: itemUrl!, completionHandler: { (location, response, error) -> Void in
guard let tempLocation = location, error == nil else { return }
do {
// after downloading your file you need to move it to your destination url
try FileManager.default.moveItem(at: tempLocation, to: destinationUrl)
print("File moved to documents folder")
completion(true, destinationUrl)
} catch let error as NSError {
print(error.localizedDescription)
completion(false, nil)
}
}).resume()
}
}
}
//MARK:- QLPreviewController Datasource
extension ViewController: QLPreviewControllerDataSource {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
controller.navigationItem.rightBarButtonItem = nil
return self.previewItem as QLPreviewItem
}
}
Please provide your suggestion to do so or any other framework to view different file formats.
Here is the image
Find below adopted my approach to your code (with modifications to test locally, but the code should be clear). The idea is
a) to override, which is completely allowed by API, needed classes to intercept modification
b) to use intentionally own UINavigationController, as only one navigation controller can be in stack
So here is code:
// Custom navigation item that just blocks adding right items
class MyUINavigationItem: UINavigationItem {
override func setRightBarButtonItems(_ items: [UIBarButtonItem]?, animated: Bool) {
// forbidden to add anything to right
}
}
// custom preview controller that provides own navigation item
class MyQLPreviewController: QLPreviewController {
private let item = MyUINavigationItem(title: "")
override var navigationItem: UINavigationItem {
get { return item }
}
}
class MyViewController : UIViewController, QLPreviewControllerDataSource {
lazy var previewItem = NSURL()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
// just stub testing code
let button = UIButton(type: .roundedRect)
button.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
button.setTitle("Show", for: .normal)
button.addTarget(self, action:
#selector(displayLocalFile(_:)), for: .touchDown)
view.addSubview(button)
self.view = view
}
#objc func displayLocalFile(_ sender: UIButton){
let previewController = MyQLPreviewController() // << custom preview
// now navigation item is fully customizable
previewController.navigationItem.title = "samplePDF.pdf"
previewController.navigationItem.leftBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .done, target: self,
action: #selector(closePreview(_:)))
// wrap it into navigation controller
let navigationController = UINavigationController(rootViewController: previewController)
// Set the preview item to display
self.previewItem = self.getPreviewItem(withName: "samplePDF.pdf")
previewController.dataSource = self
// present navigation controller with preview
self.present(navigationController, animated: true, completion: nil)
}
#objc func closePreview(_ sender: Any?) {
self.dismiss(animated: true) // << dismiss preview
}
func getPreviewItem(withName name: String) -> NSURL{
// Code to diplay file from the app bundle
let file = name.components(separatedBy: ".")
let path = Bundle(for: type(of: self)).path(forResource: file.first!, ofType: file.last!)
let url = NSURL(fileURLWithPath: path!)
return url
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return self.previewItem as QLPreviewItem
}
}

Get selected text from custom UIMenuItem in WKWebView

I am creating an iOS app that has a custom UIMenuItem. This new custom UIMenuItem shows up when text is selected in WKWebView. How do I get the selected text.
I followed instructions in https://stackoverflow.com/a/49761522/6828076 to create a custom UIMenuItem. It works fine, but I need the selected text that was used when the custom UIMenuItem was tapped. There are many posts about using UIPasteboard but the custom item does not copy the selected text into the UIPasteboard, so I am unable to retrieve it.
func setupCustomMenu() {
let customMenuItem = UIMenuItem(title: "Foo", action:
#selector(ViewController.transelateMenuTapped))
UIMenuController.shared.menuItems = [customMenuItem]
UIMenuController.shared.update()
}
#objc func transelateMenuTapped() {
let yay = //Need to retrieve the selected text here
let alertView = UIAlertController(title: "Yay!!", message: yay, preferredStyle: .alert)
alertView.addAction(UIAlertAction(title: "cool", style: .default, handler: nil))
present(alertView, animated: true, completion: nil)
}
You can use Javascript for that.
Here's the code from the the answer you used, slightly altered to get the selected text by evaluating Javascript on the WKWebView:
import UIKit
import WebKit
class ViewController: UIViewController {
weak var webView: CustomMenuWebView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
prepareWebView()
}
#objc func translateMenuTapped(_ test: Any) {
webView.evaluateJavaScript("window.getSelection().toString()") { (test, error) in
guard let test = test as? String, error == nil else { return }
// ***** Here's the user's selected text *****
print(test)
}
}
}
private extension ViewController {
func prepareWebView() {
addWebViewToView()
loadWebViewContent()
setupCustomMenu()
}
func addWebViewToView() {
let webView = CustomMenuWebView(
frame: view.bounds, configuration: WKWebViewConfiguration())
view.addSubview(webView)
self.webView = webView
}
func loadWebViewContent() {
let url = URL(string: "https://www.google.com")
let request = URLRequest(url: url!)
webView.load(request)
}
func setupCustomMenu() {
let customMenuItem = UIMenuItem(
title: "Translate", action: #selector(ViewController.translateMenuTapped))
UIMenuController.shared.menuItems = [ customMenuItem ]
UIMenuController.shared.update()
}
}
class CustomMenuWebView: WKWebView {
// Turn off all other menu items
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}

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)
}

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