iOS 8 share extension doesn't work with iBooks - ios

I try to share selected/highlighted text in iBooks with my custom extension, but it doen't have anything inside self.extensionContext
<NSExtensionContext: 0x17541d90> - UUID: <__NSConcreteUUID 0x1765e860> D69F0393-C5F1-4DEB-9A97-B479C2BC0C95 - _isHost: NO
items:
(
)
so after i choose my extension in provided list it just pops up empty SLComposeServiceViewController
Mail, iMessages, Twitter etc. works as expected. Is there any additional magic i must do to handle this?

The interesting things about this are
When sharing from iBooks, self.extensionContext.inputItems is an empty array. It's not giving you anything to share.
Sharing from iBooks only works normally with extensions that were provided by Apple. On my iPhone other extensions are available from iBooks-- Evernote, Things, and others-- but none of them work normally. They all come up with empty content.
My take: Sharing from Apple's extensions relies on some undocumented secret behavior, and there's no extra magic you can apply that would get through the app store approval process.
If you set your activation rule to TRUEPREDICATE (which means that the extension should always show up) or something very lenient, your extension will show up in iBooks. But it doesn't look like you can get any content to share right now. I'd file a bug with Apple about it.

It does seem like that the issue has been resolved with iOS 9, the following code (in Swift) correctly returns the contents of the selection in iBooks:
for item: AnyObject in self.extensionContext!.inputItems {
let inputItem = item as! NSExtensionItem
for provider: AnyObject in inputItem.attachments! {
let itemProvider = provider as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
itemProvider.loadItemForTypeIdentifier(kUTTypeText as String, options: nil, completionHandler: { (txt, error) in
NSOperationQueue.mainQueue().addOperationWithBlock {
//Doing stuff with txt
}
})
}
}
}

Related

Swift iOS check if a file has been downloaded from json

I have an app that displays and plays a list of podcasts that is fetched from a json file, I would like to add a download feature but to do this I would like to only show a download icon if the podcast has not been downloaded already, is there a way that I can save something like the id element as well as the podcast title so I could then check to see if its been downloaded and saved on the phone already? Or is there an easier way? Obviously I would like to do this before the list is shown.
You can use UserDefaults for that.
Here's an example on how to read an array from UserDefaults
// Retrieves an array of strings from disk
func getDowloadedPodcasts() -> [String] {
UserDefaults.standard.array(forKey: "downloadedPodcasts") as? [String] ?? []
}
And here's an example on how to append a new value to an array on UserDefaults
func addDownloadedPodcast(podcastId: String) {
let downloadedPodcasts = getDowloadedPodcasts()
downloadedPodcasts.append(podcastId)
UserDefaults.standard.setValue(podcastId, forKey: "downloadedPodcasts")
}
Note that this functions alone won't solve your problem nor are the best solution of your problem, they are here jsut to show how easy it can be to work with UserDefaults and to read/write from non-volatile memory

What is the purpose of `returningItems` in `NSExtensionContext.completeRequest`?

I'm working on a standard Action Extension in my iOS app and the Xcode template contains the lines:
#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)
}
Here is the documentation for completeRequest.
Questions
What is the purpose of passing returningItems to completeRequest?
Do apps actually receive edited content from an action extension?
If yes, where can I find the API on the receiving end?
What are the consequences for me to pass an empty array?
I made small research for other question, that may be found helpful here.
Shortly:
There is callback in UIActivityViewController with parameter:
returnedItems - An array of NSExtensionItem objects containing any modified data. Use the items in this array to get any changes made to the original data by an extension

IOS 11 / "Apps-prefs=root" function not working after update to Swift 3

I'm quite new to Swift programming
I've made a simple test application that open the settings programmatically, by a specific button.
Unfortunately after the update the button, instead of opening the desired setting page (the wifi page, in this case) open only the generic setting screen
Could someone please help me understanding what exactly changed in swift 4, in order for me to fix this behaviour? Thansk a lot!
First code used - (i've either changed the iOS available to iOS 11.0, in order to match the deployment)
if let url = URL(string:"App-Prefs:root=WIFI") {
if UIApplication.shared.canOpenURL(url) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
}
second code used:
let url = URL(string: "App-Prefs:root=WIFI")
UIApplication.shared.openURL(url!)
EDIT
Unfortunately the question raised is different from mine - i cannot anymore open the settings of the device, the other question ask how to open directly the settings of the APP
Urls like App-Prefs:root are prohibited in App Store and using it causing app rejection.
If you want open app preferences you could use UIApplicationOpenSettingsURLString

Failed to send custom object using WatchConnectivity (swift)

I was trying to pass my swift object from the iOS app to the Watch. However, I found it works for basic types like NSString, but my custom object type.
My custom object is able to cast to NSData
I've made my object implement NSObject and NSCoding, which works well. I can do following without problem:
let encodedChordProgression = NSKeyedArchiver.archivedDataWithRootObject(chordProgressions[1])
let decodedChordProgression = NSKeyedUnarchiver.unarchiveObjectWithData(encodedChordProgression) as! ChordProgression
NSLog("get decodedChordProgression = \(decodedChordProgression.description)")
WatchConnectivity code works for NSString
In iPhone:
try WatchSessionManager.sharedManager.updateApplicationContext(["data": "mystringishere"])
with Watch:
dispatch_async(dispatch_get_main_queue()) { [weak self] in
self?.dataSourceChangedDelegates.forEach { $0.dataSourceDidUpdate(applicationContext["data"] as! NSString)}
}
works.
My custom object with WatchConnectivity Failed
However, when I switch the object to my own object, it failed by not calling the dataSourceChangedDelegates callback function. That is:
In iPhone:
let encodedChordProgression = NSKeyedArchiver.archivedDataWithRootObject(chordProgressions[1])
try WatchSessionManager.sharedManager.updateApplicationContext(["data": encodedChordProgression])
with Watch:
dispatch_async(dispatch_get_main_queue()) { [weak self] in
self?.dataSourceChangedDelegates.forEach { $0.dataSourceDidUpdate(applicationContext["data"] as! NSData)}
}
and
func dataSourceDidUpdate(encodedChordProgression: NSData) {
let chordProgression = NSKeyedUnarchiver.unarchiveObjectWithData(encodedChordProgression) as! ChordProgression
NSLog("get something here: \(chordProgression.description)")
}
What I've tried & my problem
I've tried to read the system.log of both the iPhone app and Watch app, but I couldn't find any clue, which is the biggest problem I have now.
The full code is: here (checkout 7f2a72c6004f6580e2a38a2d7fd0ed2cef8a2b2e)
NSKeyedArchiver/NSKeyedUnarchiver won't work in this way unfortunately. This is because even though you may share class files between your watchkit and iOS targets, they are essentially different classes to the compiler because they are compiled for different architectures.
What I have done to get around this issue myself (because I initially tried to do the same thing) is serialize my custom objects to a json dictionary (or json NSData if you like) and send that. Here is a github project I have made that automatically serializes your swift objects to json for you (specifically with this use case in mind).
I tried with "NSKeyedArchiver/NSKeyedUnarchiver" and this is working perfectly.
no need to go for serialization and all.
your dictionary should have same type of data and Archiver is doing it very well.

Export audiofiles via “open in:” from Voice Memos App

I have the exact same issue as "Paul" posted here: Can not export audiofiles via "open in:" from Voice Memos App - no answers have yet been posted on this topic.
Essentially what I'm trying to do is simple:
After having recorded a Voice Memo on iOS, I select "Open With" and from the popup that is shown I want to be able to select my app.
I've tried everything I can think of and experimented with LSItemContentTypes without success.
Unfortunately I don't have enough reputation to comment on the existing post above, and I'm getting quite desperate for a solution to this. Any help is hugely appreciated, even just to know whether it's doable or not.
Thanks!
After some experimentation and much guidance from this blog post ( http://www.theappguruz.com/blog/share-extension-in-ios-8 ), it appears that it is possible to do this using a combination of app extensions (specifically an Action Extension) and app groups. I'll describe the first part which will enable you to get your recording from Voice Memos to your app extension. The second part -- getting the recording from the app extension to the containing app (your "main" app) -- can be done using app groups; please consult the blog post above for how to do this.
Create a new target within your project for the app extension, by selecting File > New > Target... from Xcode's menu. In the dialog box that prompts you to "Choose a template for your new target:" choose the "Action Extension" and click "Next".
CAUTION: Do not choose the "Share Extension" as is done in the blog post example above. That approach is more appropriate for sharing with another user or posting to a website.
Fill in the "Product Name:" for your Action Extension, e.g., MyActionExtension. Also, for "Action Type:" I selected "Presents User Interface" because this is the way Dropbox appears to do it. Selecting this option adds a view controller (ActionViewController) and storyboard (Maininterface.storyboard) to your app extension. The view controller is a good place to provide feedback to the user and to give the user an opportunity to rename the audio file before exporting it to your app.
Click "Finish." You will be prompted to "Activate “MyActionExtension” scheme?". Click "Activate" and this new scheme will be made active. Building it will build both the action extension and the containing app.
Click the disclosure triangle for the "MyActionExtension" folder in the Project Navigator (Cmd-0) to reveal the newly-created storyboard, ActionViewController source file(s), and Info.plist. You will need to customize these files for your needs. But for now ...
Build and run the scheme you just created. You will be prompted to "Choose an app to run:". Select "Voice Memos" from the list and click "Run". (You will probably need a physical device for this; I don't think the simulator has Voice Memos on it.) This will build and deploy your action extension (and its containing app) to your device. and then proceed to launch "Voice Memos" on your device. If you now make a recording with "Voice Memos" and then attempt to share it, you should see your action extension (with a blank icon) in the bottom row. If you don't see it there, tap on the "More" button in that row and set the switch for your action extension to "On". Tapping on your action extension will just bring up an empty view with a "Done" button. The template code looks for an image file, and finding none does nothing. We'll fix this in the next step.
Edit ActionViewController.swift to make the following changes:
6a. Add import statements for AVFoundation and AVKit near the top of the file:
// the next two imports are only necessary because (for our sample code)
// we have chosen to present and play the audio in our app extension.
// if all we are going to be doing is handing the audio file off to the
// containing app (the usual scenario), we won't need these two frameworks
// in our app extension.
import AVFoundation
import AVKit
6b. Replace the entirety of override func viewDidLoad() {...} with the following:
override func viewDidLoad() {
super.viewDidLoad()
// Get the item[s] we're handling from the extension context.
// For example, look for an image and place it into an image view.
// Replace this with something appropriate for the type[s] your extension supports.
print("self.extensionContext!.inputItems = (self.extensionContext!.inputItems)")
var audioFound :Bool = false
for inputItem: AnyObject in self.extensionContext!.inputItems {
let extensionItem = inputItem as! NSExtensionItem
for attachment: AnyObject in extensionItem.attachments! {
print("attachment = \(attachment)")
let itemProvider = attachment as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMPEG4Audio as String)
//|| itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMP3 as String)
// the audio format(s) we expect to receive and that we can handle
{
itemProvider.loadItemForTypeIdentifier(kUTTypeMPEG4Audio as String,
options: nil, completionHandler: { (audioURL, error) in
NSOperationQueue.mainQueue().addOperationWithBlock {
if let audioURL = audioURL as? NSURL {
// in our sample code we just present and play the audio in our app extension
let theAVPlayer :AVPlayer = AVPlayer(URL: audioURL)
let theAVPlayerViewController :AVPlayerViewController = AVPlayerViewController()
theAVPlayerViewController.player = theAVPlayer
self.presentViewController(theAVPlayerViewController, animated: true) {
theAVPlayerViewController.player!.play()
}
}
}
})
audioFound = true
break
}
}
if (audioFound) {
break // we only handle one audio recording at a time, so stop looking for more
}
}
}
6c. Build and run as in the previous step. This time, tapping on your action extension will bring up the same view controller as before but now overlaid with the AVPlayerViewController instance containing and playing your audio recording. Also, the two print() statements I've inserted in the code should give output that looks something like the following:
self.extensionContext!.inputItems = [<NSExtensionItem: 0x127d54790> - userInfo: {
NSExtensionItemAttachmentsKey = (
"<NSItemProvider: 0x127d533c0> {types = (\n \"public.file-url\",\n \"com.apple.m4a-audio\"\n)}"
);
}]
attachment = <NSItemProvider: 0x127d533c0> {types = (
"public.file-url",
"com.apple.m4a-audio"
)}
Make the following changes to the action extension's Info.plist file:
7a. The Bundle display name defaults to whatever name you gave your action extension (MyActionExtension in this example). You might wish to change this to Save to MyApp. (By way of comparison, Dropbox uses Save to Dropbox.)
7b. Insert a line for the key CFBundleIconFile and set it to Type String (2nd column), and set its value to MyActionIcon or some such. You will then need to provide the corresponding 5 icon files. In our example, these would be: MyActionIcon.png, MyActionIcon#2x.png, MyActionIcon#3x.png, MyActionIcon~ipad.png, and MyActionIcon#2x~ipad.png. (These icons should be 60x60 points for iphone and 76x76 points for ipad. Only the alpha channel is used to determine which pixels are gray, the RGB channels are ignored.) Add these icon files to your app extension's bundle, NOT the containing app's bundle.
7c. At some point you will need to set the value for the key NSExtension > NSExtensionAttributes > NSExtensionActivationRule to something other than TRUEPREDICATE. If you want your action extension to only be activated for audio files, and not for video files, pdf files, etc., this is where you would specify such a predicate.
The above takes care of getting the audio recording from Voice Memos to your app extension. Below is an outline of how to get the audio recording from the app extension to the containing app. (I'll flesh it out later, time permitting.) This blog post ( http://www.theappguruz.com/blog/ios8-app-groups ) might also be useful.
Set up your app to use App Groups. Open the Project Navigator (Cmd-0) and click on the first line to show your project and targets. Select the target for your app, click on the "Capabilities" tab, look for the App Groups capability, and set its switch to "On". Once the various entitlements have been added, click on the "+" sign to add your App Group, giving it a name like group.com.mycompany.myapp.sharedcontainer. (It must begin with group. and should probably use some form of reverse-DNS naming.)
Repeat the above for your app extension's target, giving it the same name as above (group.com.mycompany.myapp.sharedcontainer).
Now you can write the url of the audio recording to the app group's shared container from the app extension side. In ActionViewController.swift, replace the code fragment that instantiates and presents the AVPlayerViewController with the following:
let sharedContainerDefaults = NSUserDefaults.init(suiteName:
"group.com.mycompany.myapp.sharedcontainer") // must match the name chosen above
sharedContainerDefaults?.setURL(audioURL, forKey: "SharedAudioURLKey")
sharedContainerDefaults?.synchronize()
Similarly, you can read the url of the audio recording from the containing app's side using something like this:
let sharedContainerDefaults = NSUserDefaults.init(suiteName:
"group.com.mycompany.myapp.sharedcontainer") // must match the name chosen above
let audioURL :NSURL? = sharedContainerDefaults?.URLForKey("SharedAudioURLKey")
From here, you can copy the audio file into your app's sandbox, e.g., your app's Documents directory or your app's NSTemporaryDiretory(). Read this blog post ( http://www.atomicbird.com/blog/sharing-with-app-extensions ) for ideas on how to do this in a coordinated fashion using NSFileCoordinator.
References:
Creating an App Extension
Sharing Data with Your Containing App

Resources