I am building a social networking app just like instagram. In this app you can take a photo and share inside the app.
I wanted to add my app to "share with..." menu inside the gallery.
Currently I added an app extension, and my app appears on that menu.
However what I want to do is, send the selected image to main application and handle it there. (If user is logged in go to post create screen, if not first login then go create screen.)
How to achieve this goal? I tried launching the app with URL schemes but wasn't able to pass selected image.
Here is code so far:
import UIKit
import Social
import MobileCoreServices
class ShareViewController: SLComposeServiceViewController {
let MAX_SHARE_TEXT_LENGTH = 25
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
//Validate text.
if let currentMessage = contentText {
let currentMessageLength = currentMessage.characters.count
charactersRemaining = MAX_SHARE_TEXT_LENGTH - currentMessageLength
if Int(charactersRemaining) < 0 {
return false
}
}
return true
}
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
self.extensionContext!.completeRequestReturningItems([], completionHandler: nil)
}
override func configurationItems() -> [AnyObject]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
print(extensionContext)
return []
}
}
Related
I’m trying to integrate Siri Shortcuts to my application. The concept which I’m trying is to get reward points of my card with secret pin confirmation. Please find what I have done for this below.
Enabled Siri in capabilities and added Siri Intent definition file.
Added new custom intent named say Rewards.
Defined the title. Subtitle and params(accType, pin) with confirmation enabled. Pin will be sent separately to user.
Then defined the intent response with param ‘rewardPoints’ and defined the response messages.
Added Siri intent extensions.
Added custom intent to info.plist files within project and intent extension.
Verified and added new handler for the custom intent and define the resolve, handle and confirm methods as below. For now, I’m returning random no for reward points.
//
// RewardsIntentHandler.swift
// SiriIntentExt
//
import UIKit
import Intents
class RewardsIntentHandler: NSObject, RewardsIntentHandling {
func resolveAccType(for intent:RewardsIntent, with completion: #escaping ([INStringResolutionResult]) -> Void) {
guard let accType = intent.accType else {
completion([INStringResolutionResult.needsValue()])
return
}
completion([INStringResolutionResult.success(with: accType)])
}
func resolvePin(for intent:RewardsIntent, with completion: #escaping ([INIntegerResolutionResult]) -> Void) {
guard let verifyPin = intent.pin else {
completion([INIntegerResolutionResult.needsValue()])
return
}
completion([INIntegerResolutionResult.confirmationRequired(with: verifyPin as? Int)])
}
func confirm(intent: RewardsIntent, completion: #escaping (RewardsIntentResponse) -> Void) {
completion(RewardsIntentResponse.init(code: RewardsIntentResponseCode.ready, userActivity: nil))
}
func handle(intent: RewardsIntent, completion: #escaping (RewardsIntentResponse) -> Void) {
guard intent.accType != nil else {
completion(RewardsIntentResponse.init(code: RewardsIntentResponseCode.continueInApp, userActivity: nil))
return
}
guard intent.pin != nil else {
completion(RewardsIntentResponse.init(code: RewardsIntentResponseCode.continueInApp, userActivity: nil))
return
}
let response = RewardsIntentResponse.success(rewardPoints: NSNumber(value: 3453))
completion(response)
}
}
Modified the IntentHandler to return rewards handler for rewards intent
//
// IntentHandler.swift
// SiriIntentExt
//
import Intents
class IntentHandler: INExtension {
override func handler(for intent: INIntent) -> Any {
if intent is RewardsIntent {
return RewardsIntentHandler()
}
return self
}
}
Donated the intent on view load as below.
//
// ViewController.swift
// Shortcuts
//
import UIKit
import Intents
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
siriAuthorisarion()
donateRewardIntent()
}
func siriAuthorisarion() {
INPreferences.requestSiriAuthorization { (status) in
print("Siri Authorization Status - ", status)
}
}
func donateRewardIntent() {
let rewardsIntent = RewardsIntent()
rewardsIntent.suggestedInvocationPhrase = "Reward Points"
rewardsIntent.accType = "test account"
let interaction = INInteraction(intent: rewardsIntent, response: nil)
interaction.donate { error in
if let error = error {
print("Donating intent failed with error \(error)")
}
DispatchQueue.main.async {
let alert = UIAlertController.init(title: ((error != nil) ? "Error" : "Success"), message: ((error != nil) ? "Oops!!! Error occured on donating intent." : "Intent donated succussfully!!!"), preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
}
I'm facing problem from the above code base. Siri is not requesting for pin and not able to get the exact reward points for the account.
Have following questions.
Can we add the intents programmatically to Siri instead adding from shortcuts app or settings. So that user can directly use the functionality once installing the application.
Once intent is added using Shortcuts app, I’m trying the ask Siri for reward points. Its immediately requesting for my app shortcuts defined. Once we say 'yes' to request, I need to be asked for pin. But Siri replies with some problem with my app. What to be done for asking for next param value.
In the handler file, I have added the resolve methods for each parameters. I feel, resolve methods are not getting called to validate the values. Do we need to handle anything to make resolve methods work?
How can I debug the handler implementation using breakpoint within resolve/handle/confirm methods.
Thanks in advance.
Find my analysis for the above questions.
Can we add the intents programmatically to Siri instead adding from shortcuts app or settings. So that user can directly use the functionality once installing the application.
By default, intents are provided for specific domains such as messaging, payments, photos, workout, etc. No need to explicitly add intents through shortcuts for theses specific domains. Apart from these domains if we are creating custom intent, we are in need to donate and add the intents to Siri using shortcut/settings application.
Once intent is added using Shortcuts app, I’m trying the ask Siri for reward points. Its immediately requesting for my app shortcuts defined. Once we say 'yes' to request, I need to be asked for pin. But Siri replies with some problem with my app. What to be done for asking for next param value.
From iOS13, Apple has added Siri parameters and Siri suggestion for custom intents to request the missing parameters. Till iOS12, we don't have parameters option for custom intents.
In the handler file, I have added the resolve methods for each parameters. I feel, resolve methods are not getting called to validate the values. Do we need to handle anything to make resolve methods work?
In iOS12, we cannot add resolve methods for parameters in custom intents. Resolve methods handled only for specific domains provided within Intents extensions as mentioned in question 1. From iOS13, we can have resolve methods for custom intents based on the parameters.
How can I debug the handler implementation using breakpoint within resolve/handle/confirm methods.
We can add breakpoints and debug intent handler methods.
Thanks.
I have a tableView that utilizes the UIActivityViewController so users can share content on other 3rd party networks Facebook, twitter, text message etc. I have a function called IncreaseShareByOne() and it's purpose is to count the number of times that something has been shared . My problem is that right now that function fires of every time the UIActivityViewController is clicked . Is there someway that I can find out if a user really shared something so that I can use that function correctly ? becomes sometimes you open the UIActivityViewController and decide not to share anything so I want to catch and not use that function. I am new to swift and am using version 3 .
func sharedShareAction(controller: UIViewController, sender: UIButton) {
controller.present(activityViewController,animated: true,completion: nil)
IncreaseShareByOne()
}
You can add a completion handler to your UIActivityViewController. In the completion handler, check whether the user submitted using the completed value. You'll probably want to do something like this:
func sharedShareAction(controller: UIViewController, sender: UIButton) {
controller.present(activityViewController,animated: true, completion: nil)
activityViewController.completionWithItemsHandler = { activity, completed, items, error in
if !completed {
// handle task not completed
return
}
IncreaseShareByOne()
}
}
Check the API docs for more info.
I use the following codes for printing in the app:
init() {
self.printInfo.outputType = UIPrintInfoOutputType.photo
self.printInfo.orientation = UIPrintInfoOrientation.landscape
self.printController.printInfo = self.printInfo
self.printer = UIPrinter(url: URL(string: printIP)!)
// where printIP is a string that give the internal IP of the printer
debugPrint(printIP)
}
func print(image: UIImage) -> Bool {
self.printController.printingItem = image
printController.print(to: printer, completionHandler: {(controller, success, error) -> Void in
if success {
debugPrint("Printing Completed.")
} else {
debugPrint("Printing Failed.")
}
})
return true
}
It can print successfully. However, when the function is triggered, there is an alert box indicating that it is contacting to the Printer, and printing. Is there any method to avoid the pop up of this alert box? I want the printing done at the back without showing anything on the screen that interfere the user experience (I want to play a movie when the printer is working at the back).
Thanks.
From iOS 8 there is a way to print without any presentation of the
printing UI. Instead of presenting the UI each time the user presses a
print button, you can provide a way for your users to select a printer
somewhere in your app with the easy-to-use UIPrinterPickerController.
It accepts an optional UIPrinter instance in its constructor for a
pre-selection, uses the same presentation options as explained above,
and has a completion handler for when the user has selected her
printer:
Swift 3
let printerPicker = UIPrinterPickerController(initiallySelectedPrinter: savedPrinter)
printerPicker.present(animated: true) {
(printerPicker, userDidSelect, error) in
if userDidSelect {
self.savedPrinter = printerPicker.selectedPrinter
}
}
Now you can tell your UIPrintInteractionController to print directly by calling printToPrinter(:completionHandler:) with the saved printer instead of using one of the present... methods.
Source:- http://nshipster.com/uiprintinteractioncontroller/
I know that there's a built-in template for it.
I go to the File menu and choose New > Target
Select iOS > Application extensions from the left-hand pane.
Now choose Intents extension.
That will create two new groups: YourExtension and YourExtensionUI. If you open the YourExtension group you'll see IntentHandler.swift, which contains some sample code for handling workouts.
Here's a much simpler example to get you started:
class IntentHandler: INExtension, INSendMessageIntentHandling {
override func handler(for intent: INIntent) -> AnyObject {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
return self
}
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
print("Send message: " + (intent.content ?? "No message"))
let response = INSendMessageIntentResponse(code: .success, userActivity: nil)
completion(response)
}
}
I did that, it's OK.
Now my issue is about using INStartWorkoutIntent instead of INSendMessageIntent, how am I supposed to? Is there a built-in template for this intents too?
Finally, I solved the question by myself.
When you want to use INStartWorkoutIntent properly, you have just to remove all the built-in template content.
You have also to replace INSendMessageIntentHandling by INStartWorkoutIntent Handling.
public func handle(startWorkout intent: INStartWorkoutIntent, completion: #escaping (INStartWorkoutIntentResponse) -> Swift.Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartWorkoutIntent.self))
let response = INStartWorkoutIntentResponse(code: .continueInApp, userActivity: userActivity)
completion(response)
}
DO NOT FORGET:
To your newly created Intents target, fully expand the NSExtension dictionary to study its contents. The dictionary describes in more detail which intents your extension supports and if you want to allow the user to invoke an intent while the device is locked.
Insert the most relevant intents at the top if you want to support more than one. Siri uses this order to figure out which one the user wants to use in case of ambiguity.
We now need to define which intents we want to support.
For example, I want to build an extension that supports the payment intent. Modify the Info.plist file to match the following picture.
Here we specify that we want to handle the INSendPaymentIntent and that we require the device to be unlocked. We don't want strangers to send payments when the device is lost or stolen!
Last thing just to set in target your Intent at the running and it's done.
Context
I've been playing around with Today Extensions using this example project.
The app is quite simple:
In the containing app, you have a list of todo items, which you can mark completed
In the Today widget, you see the same list, but you can switch between completed, and incomplete items using a segmented control.
My goal is the following: whenever there is a data change, either in the container app, or the widget, I want both to reflect the changes:
If I mark an item as completed in the container app, then pull down the Notification Center, the widget should be updated
When I do the same in the widget, then return to the app, the app's state should be updated
The implementation
I understand, that the container app, and the extension run in their separate processes, which means two constraints:
NSUserDefaultsDidChangeNotification is useless.
Managing the model instances in memory is useless.
I also know, that in order to access a shared container, both targets must opt-in to the App Groups entitlements under the same group Id.
The data access is managed by an embedded framework, TodoKit. Instead of keeping properties in memory, it goes straight to NSUserDefaults for the appropriate values:
public struct ShoppingItemStore: ShoppingStoreType {
private let defaultItems = [
ShoppingItem(name: "Coffee"),
ShoppingItem(name: "Banana"),
]
private let defaults = NSUserDefaults(suiteName: appGroupId)
public init() {}
public func items() -> [ShoppingItem] {
if let loaded = loadItems() {
return loaded
} else {
return defaultItems
}
}
public func toggleItem(item: ShoppingItem) {
let initial = items()
let updated = initial.map { original -> ShoppingItem in
return original == item ?
ShoppingItem(name: original.name, status: !original.status) : original
}
saveItems(updated)
}
private func saveItems(items: [ShoppingItem]) {
let boxedItems = items.map { item -> [String : Bool] in
return [item.name : item.status]
}
defaults?.setValue(boxedItems, forKey: savedDataKey)
defaults?.synchronize()
}
private func loadItems() -> [ShoppingItem]? {
if let loaded = defaults?.valueForKey(savedDataKey) as? [[String : Bool]] {
let unboxed = loaded.map { dict -> ShoppingItem in
return ShoppingItem(name: dict.keys.first!, status: dict.values.first!)
}
return unboxed
}
return nil
}
}
The problem
Here's what works:
When I modify the list in my main app, then stop the simulator, and then launch the Today target from Xcode, it reflects the correct state. This is true vice-versa.
This verifies, that my app group is set up correctly.
However, when I change something in the main app, then pull down the Notification Center, it is completely out of sync. And this is the part, which I don't understand.
My views get their data straight from the shared container. Whenever a change happens, I immediately update the data in the shared container.
What am I missing? How can I sync up these two properly? My data access class is not managint any state, yet I don't understand why it doesn't behave correctly.
Additional info
I know about MMWormhole. Unfortunately this is not an option for me, since I need to reach proper functionality without including any third party solutions.
This terrific article, covers the topic, and it might be possible, that I need to employ NSFilePresenter, although it seems cumbersome, and I don't completely understand the mechanism yet. I really hope, there is an easier solution, than this one.
Well, I have learned two things here:
First of all, Always double check your entitlements, mine somehow got messed up, and that's why the shared container behaved so awkwardly.
Second:
Although viewWillAppear(_:) is not called, when you dismiss the notification center, it's still possible to trigger an update from your app delegate:
func applicationDidBecomeActive(application: UIApplication) {
NSNotificationCenter.defaultCenter().postNotificationName(updateDataNotification, object: nil)
}
Then in your view controller:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserverForName(updateDataNotification, object: nil, queue: NSOperationQueue.mainQueue()) { (_) -> Void in
self.tableView.reloadData()
}
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Updating your Today widget is simple: each time the notification center is pulled down, viewWillAppear(:_) is called, so you can query for new data there.
I'll update the example project on GitHub shortly.