How to share selected text with my application? - ios

I would like to make my app appear in the UIActivityViewController for text sharing, like in Mail, iMessage, Notes, Gmail etc etc.
For example, when user tapback on selected text and hit the 'Share' button from any app like in the attachment:
I would like my app to appear in the UIActivityViewController and when the user selects my app, to launch it with the ability to handle that selected text.
So what I have tried:
Search in Apple documentation.
Searched for relevant UTI but I understood that the UTIs are used only for files and not for a simple NSString (Correct me if I'm wrong).
I have tried to implement Share Extension, but this is not the solution that i want, I don't need the post popup and moreover than that I need to launch my app after sharing like in Mail, Notes, iMessage does (Regarding to Apple documentation we can't launch the contain app through Share extension only through Today extension).
Of course, I have searched a lot in StackOverFlow.
So any solutions?
Thanks!

Assume that you already have some app.
Add Target File -> New -> Target. In
the left pane, select Application Extension from the iOS section,
choose Action extension, and click Next.
Set the Product Name. Make sure the Action Type is Presents user interface. Click Finish.
In the Project Navigator, expand the extension group and click on MainInterface.storyboard. Select the image view and replace it with UITextView. Create and bind #IBOutlet weak var to it.
Select Info.plist of the extension, navigate to NSExtension -> NSExtensionAttributes -> NSExtensionActivationRule. Change the NSExtensionActivationRule's type from String to Dictionary. With the dictionary expanded, click the + button next to it. This will add a child key. Set its name to NSExtensionActivationSupportsText, its type to Boolean, and the value to YES. This ensures that the action extension is only visible when at least one input item contains text.
Put this code to ActionViewController.swift:
_
import UIKit
import MobileCoreServices
class ActionViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Get the item[s] we're handling from the extension context.
var textFound = false
for item in self.extensionContext!.inputItems as! [NSExtensionItem] {
for provider in item.attachments! {
if provider.hasItemConformingToTypeIdentifier(kUTTypePlainText as String) {
// This is an plain Text.
weak var weakTextView = self.textView
provider.loadItem(forTypeIdentifier: kUTTypePlainText as String, options: nil, completionHandler: { (textItem, error) in
OperationQueue.main.addOperation {
if let strongTextView = weakTextView {
if let gotText = textItem as? String {
strongTextView.text = gotText
// do what you need with the text
}
}
}
})
textFound = true
break
}
}
if (textFound) {
// We only handle one text, so stop looking for more. You can do as you need.
break
}
}
}
#IBAction func done() {
// Return any edited content to the host app.
// This template doesn't do anything, so we just echo the passed in items.
self.extensionContext!.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil)
}
}

Related

Informing voiceover users that they have highlighted an element within a container

I'm looking to implement something for voiceover users that is similar to how Apple handles the miniplayer in their Music app.
In the miniplayer, there are a number of accessibility elements: the album artwork, the track metadata, the play and forward buttons. When a user first selects an element within the miniplayer, the voiceover reads "Miniplayer; double tap to expand the miniplayer" before giving the label for the element selected. But if you navigate between elements in the miniplayer, it will just give each element's label, trait and hint. It will only provide the Miniplayer (container level) label and hint when you have moved from an element outside the miniplayer to an element inside the miniplayer.
Being able to give this kind of context to voiceover users seems like good UX design, but how is this implemented? I understand how to group elements together by including them in the myItem.accessibilityElements array, but not how to determine whether the current/previous element that the user has selected is part of the same container.
To inform a VoiceOver user that an element has been selected within a container, you could create a class for each kind of element you have in the container (use of generics maybe ???) to override the methods of the UIAccessibilityFocus informal protocol.
Let's take an example assuming that we have only labels in the blue container hereunder (code to be adapted for other kinds of elements) :
Create a class for the labels including a property for the superview it belongs to.
class EltLabel : UILabel {
var containerView: UIView? {
get { return self.superview }
set { }
}
}
Override the UIAccessibilityFocus informal protocol methods in an extension of the new created class.
extension EltLabel {
override open func accessibilityElementDidBecomeFocused() {
if self.accessibilityElementIsFocused() {
// Actions to be done when the element did become focused.
}
}
override open func accessibilityElementDidLoseFocus() {
if self.accessibilityElementIsFocused() {
// Actions to be done when the element did lose focus.
}
}
override open func accessibilityElementIsFocused() -> Bool {
guard let containerView = self.containerView else { return false }
return (self.isDescendant(of: containerView)) ? true : false
}
}
Don't forget to create outlets with your labels and everything is automatically handled.
class BoutonViewController: UIViewController {
#IBOutlet weak var label1: EltLabel!
#IBOutlet weak var label2: EltLabel!
}
This code snippet to be adapted and included in your code environment will inform the VoiceOver users that they have highlighted an element within a container as desired.
Now, if you want to "determine whether the current/previous element that the user has selected is part of the same container", just add a variable in the view controller that will be incremented/decremented every time an element of the container is focused/unfocused (a boolean variable should be enough).

Dismiss keyboard in an iOS Notification Content Extension

I'm presenting custom action buttons in my iOS 11 notification window via a Notification Content Extension target. One of them is a 'comment' button. If I press it the keyboard shows up properly, but I am not able to figure out how to have the keyboard go away and get back to the other buttons on the notification. There's not really anything I can see to call resignFirstResponder on. Am I just missing something really obvious?
There is more than one way to do this.
Without A Content Extension
The first does not even require a notification content extension! The UNTextInputNotificationAction does all of the work for you. When initializing the action you specify parameters for the text field that will be presented when the action is triggered. That action is attached to your notification category during registration (i.e. inside willFinishLaunchingWithOptions):
userNotificationCenter.getNotificationCategories { (categories) in
var categories: Set<UNNotificationCategory> = categories
let inputAction: UNTextInputNotificationAction = UNTextInputNotificationAction(identifier: "org.quellish.textInput", title: "Comment", options: [], textInputButtonTitle: "Done", textInputPlaceholder: "This is awesome!")
let category: UNNotificationCategory = UNNotificationCategory(identifier: notificationCategory, actions: [inputAction], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: "Placeholder", options: [])
categories.insert(category)
userNotificationCenter.setNotificationCategories(categories)
}
This will produce an experience like this:
Note that by default, the "Done" button dismisses the keyboard and notification.
With more than one action you get this:
There is no going back to the action buttons that were presented with the notification - notifications can't do that. To see those actions choices again would require showing another notification.
With a Content Extension
First, the above section works with a content extension as well. When the user finishes entering text and hits the "textInputButton" the didReceive(_:completionHandler:) method of the content extension is called. This is an opportunity to use the input or dismiss the extension. The WWDC 2016 session Advanced Notifications describes this same use case and details ways it can be customized further.
This may not meet your needs. You may want to have a customized text entry user interface, etc. In that case it is up to your extension to handle showing and hiding the keyboard. The responder that handles text input - a UITextField, for example - should become first responder when the notification is received. Doing so will show the keyboard. Resigning first responder will hide it. This can be done inside a UITextField delegate method.
For example, this:
override var canBecomeFirstResponder: Bool {
get {
return true
}
}
func didReceive(_ notification: UNNotification) {
self.label?.text = notification.request.content.body
self.textField?.delegate = self
self.becomeFirstResponder()
self.textField?.becomeFirstResponder()
return
}
// UITextFieldDelegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.textField?.resignFirstResponder()
self.resignFirstResponder()
return true
}
Produces a result like this:
Keep in mind that on iOS 10 and 11 any taps on the notification itself - like on your text field - may result in it being dismissed! For this and many other reasons going this route is probably not desirable.

UITextField text not appearing when set by delegate method

I'm implementing a barcode scanner for my app using the google books api. The data gets fetched successfully and stored in the proper textfields:
func readInJSON(controller: UIViewController, title: String, author: String, imageLink: String) {
self.titleTextField.text = title
self.authorTextField.text = author
}
However the text does not actually appear in the textFields until I tap on the them, then the text populates. I'm wondering if there is some way to have the text appear without having a user tap on the text fields themselves.
Maybe you are not on the main thread. UI operation should be done on the main thread.
Try this
dispatch_async(dispatch_get_main_queue()) {
self.titleTextField.text = title
self.authorTextField.text = author
}

How to show an error in apple watch

How to handle exception cases like offline error in apple watch, i can not find anything about this in Apple Watch Programming Guide.
Is apple watch support overlay text? or need make error ui by myself.
WatchKit does not provide alert or error UI yet (as of March 2015). You will have to make one yourself.
One easy way is to make a custom class that implements WKInterfaceController and create the interface in Storyboard. Then use presentControllerWithName:context: to display it modally.
ErrorInterfaceController:
import WatchKit
import Foundation
class ErrorInterfaceController: WKInterfaceController {
#IBOutlet weak var errorMessageLabel: WKInterfaceLabel?
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context);
if let dictionary = context as? [String: String] {
if let message = dictionary["message"] {
errorMessageLabel!.setText(message)
}
}
}
#IBAction func closeModalView() {
dismissController()
}
}
Method to show the custom error UI modally:
private func showError(#message: String!) {
presentControllerWithName("ErrorInterfaceController", context: ["message": message]);
}
WatchKit does not provide any type of alert similar to UIAlertController you'd find in UIKit.
You can present a new interface controller and show details on that screen, then add buttons to it if the user needs to take action (for example to dismiss it).
To display error text in your WatchKit app, you can use label objects.
Labels support formatted text that can be changed programmatically at runtime.

Problems with storing UISwitch state

I have a setting in my app that I am having issues retaining the state of. Basically, the UISwitch is on by default, however when updating my app to a newer version, it seems to switch off. I figured out that if the user has opened the settings menu in version 1.0, then this isn't an issue, however if they have never changed a setting, or even opened the settings menu, then it's a problem. Therefore, it must be an issue somewhere in viewWillAppear() but I can't figure out what it is. The code I have for storing the switches state seems really overcomplicated.
This is the code I have in my settings menu:
#IBAction func dupOffOnSwitch(sender: AnyObject) {
if dupSwitch.on == true {
autoAdjust = true
println(autoAdjust)
} else {
autoAdjust = false
println(autoAdjust)
}
NSUserDefaults.standardUserDefaults().setBool(autoAdjust, forKey: "autoAdjustSettings")
}
override func viewWillAppear(animated: Bool) {
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
if autoAdjust == true {
dupSwitch.on = true
} else {
dupSwitch.on = false
}
}
if userReturnedAuto == false {
dupSwitch.on = true
themeSwitch.on = false
userReturnedAuto = true
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "userReturnedAuto")
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "autoAdjustSettings")
}
I am declaring the bool 'autoAdjust' in a different view controller as a global variable. In that same view controller in viewWillAppear() I have this code:
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
Can anyone suggest a better way of storing the switches state while also having it on by default when the app is first launched? Or a fix to my current solution.
NSUserDefaults.standardUserDefaults().boolForKey("key") defaults to false. Therefore, as in your case, if the user never sets the key, then it will be false and the switch will default to off.
Instead, rename the key and use it as the negative. So instead, call it autoAdjustSettingsOff which will default to false and the switch will default to on. Don't forget to switch around the true/false settings in your conditional blocks.
Not sure if this is over kill(or just stupid). But why not use CoreData to save all the users settings. Then when the app becomes active(in your appDelegate) you can set the NSUserDefaults from these stored values. Just a thought :)
For shortness I will only make an example for one switch, you can then fill out and add the rest. I will also assume you will be updating the NSUserDefaults when the switch changes state. We will only update/save the core data when the app goes to in-active and load settings once when app becomes active.
Also if anyone sees something I missed or could be done better please let me know and I will update the answer.
First off you need to make sure you import the CodeData libraries into your view controller:
import CoreData
class fooBarViewController: UIViewController {
You need to create and entity in core data: lets assume it is called "Settings"
then once you have created the entity, you need to add some attributes. Press the "+" symbol.
Now add the attribute and set the name to "dupSwitch" and type to "Boolean". Add an attribute for each setting you need to store.
Next: You need to create a new swift file. Type NSObject. name it something like "dataObjects".
Inside you you will use the following code: (first remove all current code - make it blank)
import UIKit
import CoreData
#objc(settingObj)
class settingObj: NSManagedObject {
#NSManaged var dupSwitch:NSNumber //do this for each setting
}
Next you need to go back to your CoreData dataModel and do the following.
1. click on "Default" under the section "Configuration"
2. You will then get a list of all your "entities" select "Settings"
3. click on class and edit the field to "settingObj"
Now that you have you CoreData set up. You can now save/edit/delete data as need be.
For example when you need to load all saved user settings when app goes will become active.
in your AppDelegate.swift file: import the CoreData libraries.
import CoreData
then add this function if it is not already present
func applicationWillEnterForeground(application: UIApplication) {
}
inside "applicationWillEnterForeground" you will load any settings that have been stored in CoreData
var results:[settingObj] = []
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let req = NSFetchRequest(entityName: "Settings")
let tapResults = context.executeFetchRequest(req, error: nil)
if let presentResults = tapResults {
if let castedResults = presentResults as? [settingObj] {
for item in castedResults {
let dupSwitch = item.dupSwitch
let settingSwitch = item.whateverElseYouNeedExample
//now based on this we can set the NSDefault
NSUserDefaults.standardUserDefaults().setBool(dupSwitch, forKey: "autoAdjustSettings")
NSUserDefaults.standardUserDefaults().setBool(settingSwitch, forKey: "whateverElseYouNeedExample")
}
}
}
Now when you want to save the users settings. I would would just it when app goes in-active.
First we will check if the settings have already been saved. If so, then we will just perform
an update. If not, we will save a new entry into yuor CoreData Entity.
add the following func to your appDelegate(if not already there):
func applicationDidEnterBackground(application: UIApplication) {
}
EDIT I HAVE TO GO TO WORK WILL COMPLETE REST OF ANWSER WHEN I GET BACK HOME. REALLY SORRY.

Resources