iOS Notification Content Extension - How to pass data to app? - ios

I wrote a custom Notification Content Extension for my Push Notifications like this:
The thing is, whenever the user is on a certain item in the carousel, I want the GO TO APP button to send a String to the app when it's opening, and from there, handle that string to move the user to the correct ViewController.
I already have the handling part inside the app, I just need to know how to pass that String from the Notification Content Extension to the container app.
Thanks! :)

Enable app groups in capabilities and use suite userDefaults to write the key and read it in the app
NSUserDefaults*defaults= [[NSUserDefaults alloc] initWithSuiteName:#"group.com.company.appName"];
// Write in extension
[defaults setObject:#"anyThing" forKey:#"sharedContent"];
// Read in app
[defaults objectForKey:#"sharedContent"];

If your app is configured for Universal Links or you have defined a Custom URL Scheme for your app, you can also open your app's URL (e.g. with data in query parameters) by calling
extensionContext?.open(url)
in your NotificationViewController.

iOS 13, Swift 5.
Based on the answer by Sh_Khan, here is some Swift Syntax. Obviously I have added App Group as a capability to the target of the main app + the target of the extension, naming the group as "group.ch.Blah" for this example.
Setting your app group, saving a string in our case, needed to set the type as Any cause strings not a type that is available in groups.
let localK = getPrivateKey64() as Any
let defaults = UserDefaults.init(suiteName: "group.ch.Blah")
defaults?.set(localK, forKey: "privateK")
Setting your app group, and reading the string back, needed to recast it back to string.
let defaults = UserDefaults.init(suiteName: "group.ch.Blah")
let localK = defaults?.object(forKey: "privateK") as? String
Worked perfectly with a notification service extension.

Related

UIPasteboard not giving value

An object that helps a user share data from one place to another within your app, and from your app to other apps.
This is the statement written at the very beginning of UIPasteboard docs. But when I try to use it in two different apps accessing data set by other app I am getting nil everytime
DispatchQueue.global(qos: .background).async {
var i = 1
while(i > 0) {
let v = UIPasteboard.general.string
sleep(1)
print("Task : \(i)")
print("Value: \(v)")
i = i + 1
}
}
I am fetching data in above code and setting data as in below code.
UIPasteboard.general.string = "Hello"
NB: I have tested locally in this app it is setting data
Are you running iOS >=10? There was a privacy change regarding passing value between apps. Try reading the UIPasteBoard api doc : (https://developer.apple.com/documentation/uikit/uipasteboard).
Tl:dr You need to have both apps to be in the same app group (Communicating and persisting data between apps with App Groups)
To note: iOS apps are sandboxed. So the change in iOS 10 just enforces that feature.
EDITED: Since you can't use App Groups (different developer and/or products), you have to send data via a different channel. Try searching urlSchemes or store/fetch through a common server(tedious tho)

How To Use Shared Container/App Groups To Access Files From Other Targets in Swift

I am new to Swift.
I am trying to make a two-app project, where one app contains some data files and the other accesses those files.
The solution, I think, has been to use the App Groups entitlements to allow for this, and access the files through those means. I have been able to follow along with the example here: Communicating and persisting data between apps with App Groups. In particular, the 2nd answer, which is Swift-ish (maybe an older version I guess?). It does seem to work with the right entitlements. So, now the question is how can I access the file from one app, with it being apart of the another? I'm not familiar with the API's and correct functions that I can use (new to Swift, as I said).
The apps are basic. Setup as single view applications, with everything defaulted except the ViewController's, entitlements, and one has the test data. The Xcode project structure is:
testingData/
testingData/
testingData.entitlements
TestData/
testdata.txt
AppDelegate.swift
ViewController.swift
Main.storyboard
Assets.xcassets
LaunchScreen.storyboard
Info.plist
sharedContainerTest/
sharedContainerTest.entitlements
AppDelegate.swift
ViewController.swift
Main.storyboard
Assets.xcassets
LaunchScreen.storyboard
Info.plist
Products/
testingData.app
sharedContainerTest.app
The entitlements are both the same. They each have App Groups enabled, with the same string: group.com.example.name. On the testingData target, the ViewController has the following chunk in the viewDidLoad function from that example (modified for Swift 4.x):
var userDefaults = UserDefaults(suiteName: "group.com.example.name")!
userDefaults.set("user12345", forKey: "userId")
userDefaults.synchronize()
On the sharedContainerTest target, its ViewContoller has
var userDefaults = UserDefaults(suiteName: "group.com.example.name")
if let testUserId = userDefaults?.object(forKey: "userId") as? String {
print("User Id: \(testUserId)")
}
in its viewDidLoad function. As I said, this seems to work, but now what do I need to add to this to access the testdata.txt file from the testingData app? Does it need to be stored as a variable, perhaps? Or is there a Bundle object that can do this?
If this has been answered elsewhere, please kindly point me to it and I'll take this down.
After some trial and error, the answer is as follows:
Instead of passing in the string "user12345", you need to pass in the URL to the file you want to read for the userDefaults.set method as follows:
var userDefaults = UserDefaults(suiteName: "group.com.example.name")!
userDefaults.set(Bundle.main.url(forResource: "testdata", withExtension: ".txt"), forKey: "userId")
userDefaults.synchronize()
Then in the receiver app, you call that object and set the URL:
let userDefaults = UserDefaults(suiteName: "group.com.example.name")
let test = userDefaults?.object(forKey: "userId") as? String
let testURL = URL(fileURLWithPath: test!)
From here you can read in the contents as normal.

NSUserDefaultsDidChangeNotification not sent

While working with data sharing between iOS app and Today Extension, I faced the problem that the NSUserDefaultsDidChangeNotification is never sent from either main app or extension when I change UserDefaults. The thing is that I can read and write data to the UserDefaults successfully for the App Group I created. So the data is actually shared by the app and extension. But the notification of the UserDefaults change is never fired (or detected). Can somebody tell me what can be an issue?
The writing of the data in the UserDefaults
NSUserDefaults defaults = new NSUserDefaults("group.com.name1.name2",NSUserDefaultsType.SuiteName);
defaults.SetString("UPDATE " + DateTime.Now.Minute, "data");
defaults.Synchronize();
The notification handler
NSNotificationCenter.DefaultCenter.AddObserver(
NSValueTransformer.UserDefaultsDidChangeNotification, (notification) => {
NSUserDefaults defaults = new NSUserDefaults("group.com.name1.name2",NSUserDefaultsType.SuiteName);
string str = defaults.StringForKey("data");
});
You can use CFNotificationCenter from your container app to post a cross-process notification to your extension app.
Shared Constants:
const string id = "group.sushihangover";
const string key = "LastUpdateTime";
Container app / Setup an observer on your NSUserDefaults object:
var todayWidgetUserDefaults = new NSUserDefaults(id, NSUserDefaultsType.SuiteName);
NSValueTransformer.Notifications.ObserveUserDefaultsDidChange(todayWidgetUserDefaults,(sender, e) =>
{
CFNotificationCenter.Darwin.PostNotification(id, todayWidgetUserDefaults, null, true, true);
});
Today Extension App:
var todayWidgetUserDefaults = new NSUserDefaults(id, NSUserDefaultsType.SuiteName);
void ObserverAction(string notificationId, NSDictionary userInfo)
{
if (notificationId == id)
{
Console.WriteLine(todayWidgetUserDefaults.StringForKey(key));
}
}
var observerToken = CFNotificationCenter.Darwin.AddObserver(id, todayWidgetUserDefaults, ObserverAction, CFNotificationSuspensionBehavior.DeliverImmediately);
Note: App Group entitlements must be setup in both your container app and the Today Extension App.
So, SushiHangover provided perfectly working piece of code.
However, the problem was in the Info.plist file. In order to be able to exchange the notification the Background Mode should be enable for the app with Remote Notification feature. It sounds like a very obvious thing to do, but none of the tutorials for exchanging data between Extension and app with User Defaults that I read mentioned this. Perhaps, it is an obvious thing to do. But I am writing this just in case somebody might have missed this thing too.

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

App launched with custom URL scheme. How do I return data to the calling app when done?

I am taking an Android programming course at my University only I have been allowed by the teacher to do IOS but I have to implement the same projects. This project is to have two apps. The first app is a color picker from a previous assignment. The second app is to call the colorpicker and allow the user to choose a color and when done return it too the second app to be displayed.
I have defined a custom URL scheme in my ColorPicker which works fine. In my second app I have a changeColor button that has the following IBAction method.
- (IBAction)colorChangePressed:(UIButton *)sender {
UIApplication *test = [ UIApplication sharedApplication ];
BOOL found =
[ test openURL:[ NSURL URLWithString:#"colorPicker://" ] ];
if (found) NSLog( #"Resource was found" );
else NSLog(#"unable to locate resource" );
}
This indeed launches the color picker app and it behaves as expected. My question is, after the color has been selected how do I return to the calling app with the selected color? I will add a finished button in my colorPicker to be clicked when the user is done selecting the color and I will capture the values I need but I can't figure out how to get this data back to the calling app. Is there some protocol/delegate pattern I need to implement?
The complete code is on git hub at. https://github.com/jnels124/CS390H
Thanks in advance for any insight as to how to solve my problem.
You need to have both apps with unique schemes. Encode the scheme of app1 and use it as a part of app1->app2 URL. When app2 is finished, you'll have a app2->app1 URL, use itto open app1 and send it required information (encoded).
It is similar as if you've put a String extra to app2 Intent with the name of app1 Intent, but instead of Intent you use URL and parse it as needed.
I defined a custom scheme in the other project as stated in the first answer but I was unsure how to generate the query string in the called URL and return it to the calling application to be parsed. I had this resolved in the following post.
Syntax for passing NSArray to other application with custom URL Scheme

Resources