Supporting Suggestions in Share Extension doesn't work - ios

I am working on feature that will add my conversations to share sheet menu when user wants to share files
I did exactly what Apple said in their guide: https://developer.apple.com/documentation/foundation/app_extension_support/supporting_suggestions_in_your_app_s_share_extension
But when I am pressing on one of my conversations in apple share screen, I want to get intent from share extension ViewController, my intent is nil
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let intent = self.extensionContext?.intent as? INSendMessageIntent {
} else {
//intent is nil
}
}
I have added INSendMessageIntent to info.plist
enter image description here
My issue is exactly as this: iOS ShareContext tapping on Suggestion Intent property of extensionContext is nil

Related

Using Markup via UIDocumentInteractionController

I am trying to support Apple's Markup of PDFs via UIDocumentInteractionController for files in my Documents folder on iPad. I want the documents edited in-place, so my app can load them again after the user is finished. I have set the Info.plist options for this, and the in-place editing does seem to work. Changes are saved to the same file.
When I bring up the UIDocumentInteractionController popover for the PDF, I am able to choose "Markup", which then shows the PDF ready for editing. I can edit it too. The problem is when I click "Done": I get a menu appear with the options "Save File To..." and "Delete PDF". No option just to close the editor or save.
The frustrating thing is, I can see via Finder that the file is actually edited in-place in the simulator, and is already saved when this menu appears. I just want the editor to disappear and not confuse the user. Ie I want "Done" to be "Done".
Perhaps related, and also annoying, is that while the markup editor is visible, there is an extra directory added to Documents called (A Document Being Saved By <<My App Name>>), and that folder is completely empty the whole time. Removing the folder during editing does not change anything.
Anyone have an idea if I am doing something wrong, or if there is a way to have the Done button simply dismiss?
In case others have this issue, I believe it is a bug in UIDocumentInteractionController in how it sets up the QLPreviewController it uses internally. If I proxy the delegate of the QLPreviewController, and return .updateContents from previewController(_:editingModeFor:), it works as expected.
Here is my solution. The objective is simple enough, but actually capturing the private QLPreviewController was not easy, and I ended up using a polling timer. There may be a better way, but I couldn't find it.
import QuickLook
class ViewController: UIViewController, UIDocumentInteractionControllerDelegate {
/// This wraps the original delegaet of the QLPreviewController,
/// so we can return .updateContents from previewController(_:editingModeFor:)
let delegateProxy = QLPreviewDelegateProxy()
var documentInteractionController = UIDocumentInteractionController()
/// A timer we use to update the QL controller
/// Ideally, we would use callbacks or delegate methds, but couldn't
/// find a satisfactory set to do the job. Instead we poll (like an animal)
var quicklookControllerPollingTimer: Timer?
/// Use this to track the preview controller created by UIDocumentInteractionController
var quicklookController: QLPreviewController?
/// File URL of the PDF we are editing
var editURL: URL!
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
quicklookControllerPollingTimer?.invalidate()
}
#IBAction func showPopover(_ sender: Any?) {
documentInteractionController.url = editURL
documentInteractionController.delegate = self
documentInteractionController.presentOptionsMenu(from: button.bounds, in: button, animated: true)
quicklookControllerPollingTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [unowned self] timer in
guard quicklookController == nil else {
if quicklookController?.view.window == nil {
quicklookController = nil
}
return
}
if let ql = presentedViewController?.presentedViewController as? QLPreviewController, ql.view.window != nil {
self.quicklookController = ql
// Extra delay gives UI time to update
DispatchQueue.main.asyncAfter(deadline: .now()+0.1) {
guard let ql = self.quicklookController else { return }
delegateProxy.originalDelegate = ql.delegate
ql.delegate = delegateProxy
ql.reloadData()
}
}
}
}
}
class QLPreviewDelegateProxy: NSObject, QLPreviewControllerDelegate {
weak var originalDelegate: QLPreviewControllerDelegate?
/// All this work is just to return .updateContents here. Doing this makes it all work properly.
/// Must be a bug in UIDocumentInteractionController
func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
.updateContents
}
}

How to add extra options to iOS Share Sheet like Safari does

I am trying to reproduce what Apple does with Safari, where there is a sharing button that allows to share the current page but also provides some extra (and internal) options to do with the page.
For example, we are given options to Add to the reading list, add as bookmark, etc.
These options are exclusive to Safari, as they are not part of the default Share Sheet. How can I hide some of my own app's functionality under a Share Sheet like that?
I did some research and I only found out about custom activities which (as I understand) are the squared buttons that are in the second row of the share sheet (partially hidden in my screenshot).
I also found out about extensions, but I don't think that helps my case, as that allows to customize the share sheet globally for every app, and I need to add options in my app's runtime.
When you initialise a share sheet (aka UIActivityController), you are asked for an array of applicationActivities, that is where your custom internal/share actions will go.
You should just subclass UIActivity, and override the required members as specified by the documentation:
activityType
activityTitle
activityImage
canPerform(withActivityItems:)
prepare(withActivityItems:)
activityCategory
To make your UIActivity appear as a menu button at the bottom of the share sheet, make sure you return .action for activityCategory. If you return .share, it will appear as a square button at the top.
Example:
// dummy implementation
class Foo: UIActivity {
override var activityTitle: String? { "Foo" }
override var activityType: UIActivity.ActivityType? { UIActivity.ActivityType("Foo") }
override var activityImage: UIImage? { UIImage(systemName: "doc.on.doc.fill") }
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
true
}
override class var activityCategory: UIActivity.Category { .action }
override func prepare(withActivityItems activityItems: [Any]) {
print("Preparing Foo!")
}
override func perform() {
print("Performed Foo!")
}
}
...
let shareSheet = UIActivityViewController(activityItems: [stuffToShare], applicationActivities: [Foo()])
Output:
If I use .share instead:

How to make a button in an extension redirect the user to a certain page

Overview
I'm building a custom keyboard extension in swift and I want to make a button redirect the user to a certain page in its parent application.
What I'm doing
I created a button in my keyboard that is supposed to take the user to the application and it works.
#objc func openURL(_ url: URL) {
return
}
func openApp(_ urlstring:String) {
var responder: UIResponder? = self as UIResponder
let selector = #selector(openURL(_:))
while responder != nil {
if responder!.responds(to: selector) && responder != self {
responder!.perform(selector, with: URL(string: urlstring)!)
return
}
responder = responder?.next
}
}
#IBAction func openShop(_ sender: Any) {
openApp ("myapppurl")
parent?.performSegue(withIdentifier: "Shop", sender: self)//Shop is the identifier of a segue in my app.
}
And of course I added this to my the info.plist file in my app.
Problem
When I press the button, it takes me to the app but it shows the main vc, not the page i need.
Question
How do I make the button take me directly to the desired vc?
You can't performSegue to container app from extension. In order to performSegue to work view needs to be in foreground. When you are performing Segue, container App is in background and that's why it is not doing anything.
You will need to handle deep-linking in container App.
Add destination inside your openApp URL "myapppurl//shop"
This should trigger openURL[enter link description here][1] in container app.
and URL param should give you requested destination. There you can handle logic to deep-linking.
You can set USERDEFAULTS value , when opening app check it, and clear.

Aren't KeychainWrapper items deleted from iPhone after we remove the application? (Swift 4.2)

I have created one single application to explain my question well.
There is a button in the page(ViewController)
Here is the ViewController.swift file
import UIKit
import SwiftKeychainWrapper
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let resultOfTheMethod = checkKeychainWrapperExistOrNot()
print("checkKeychainWrapperExistOrNot method result: \(resultOfTheMethod)")
}
#IBAction func btnPressed(_ sender: UIButton) {
saveSomeDummyDataInToTheKeychainWrapper()
}
func saveSomeDummyDataInToTheKeychainWrapper()
{
KeychainWrapper.standard.set("TEST_123", forKey: "someKey4KeychainWrapper")
}
func checkKeychainWrapperExistOrNot () -> Bool {
if let _ = KeychainWrapper.standard.string(forKey: "someKey4KeychainWrapper")
{
return true
}
return false
}
}
Let me explain what i did in order:
I have plugged my iPhone in to my mac. And then I run the app as a first time with using my iPhone. I got this output from viewDidLoad method:
checkKeychainWrapperExistOrNot method result: false
And then I have clicked the button. After that I have closed the application and opened it again. I got this output:
checkKeychainWrapperExistOrNot method result: true
And then i have done with these steps in order:
Removed the app from my iPhone,
Clicked: Product > "Clean Build Folder",
And then re run the application. I got this output:
checkKeychainWrapperExistOrNot method result: true
Result is: KeyChainWrapper item is not deleted. I would like to confirm it because it is weird little bit: I am done with this application: I have removed it from my phone. But some files still exist in my iPhone related with this removed app.
So my first question is: Aren't KeyChainWrapper items deleted when we remove the application from iPhone? When are these deleted?
Second question: How can I remove all KeychainWrapper items? Let's assume I have lots of KeychainWrapper items like this:
KeychainWrapper.standard.set("TEST_123", forKey: "someKey4KeychainWrapper")
KeychainWrapper.standard.set("TEST_123", forKey: "someKey4KeychainWrapper2")
KeychainWrapper.standard.set("TEST_123", forKey: "someKey4KeychainWrapper3")
How can I remove all of them?

XCode8 Swift3 - Problems with open contact list and retrieve data by click

Currently I'm developing my first iOS App and I'm a little slow and rude about the code (it's so weird and different from java) and, if this was the only problem, with the new update, Xcode is making my code insane. I think I solved most of the issues but...
Before, on one of the screens, the app opened a the address book and let the user click on one; when the clicked was done, the contact list close and data from that contact was retrieved to the controller. Now, if the user click on a contact, more info is displayed but any information come out of the console log.
I try everything I find on net and I'm not sure why is not working.
Before, I use Addressbook (or something like that) but I already tried with CNContact.
This is the Button code
#IBAction func addNewContactOnClick(_ sender: AnyObject) {
let peoplePicker = CNContactPickerViewController()
peoplePicker.delegate = self
self.present(peoplePicker, animated: true, completion: nil)
}
CNContactPickerDelegate methods
func contactPicker(picker: CNContactPickerViewController, didSelectContacts contacts: [CNContact]){
contacts.forEach { contact in
for number in contact.phoneNumbers {
let phoneNumber = number.value as! CNPhoneNumber
print("number is = \(phoneNumber)")
}
}
}
func contactPickerDidCancel(picker: CNContactPickerViewController) {
print("Cancel Contact Picker")
}
Methods of CNContactPickerDelegate is changed in Swift 3 like below.
func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
//your code
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
//your code
}
For other methods of CNContactPickerDelegate check Apple Documentation.

Resources