I am trying to integrate OneSignal into my app.
What I want is when user tap a notification to present desired ViewController modally. I implemented opening VC logic in handleNotificationAction while init of OneSignal.
The problem is that OneSignal still opens its WebView, and I don't want it to. Is there any way to disable opening WebView when user taps notification?
let onesignalInitSettings = [kOSSettingsKeyAutoPrompt: false]
OneSignal.initWithLaunchOptions(launchOptions,
appId: "myAppID",
handleNotificationAction: { result in
guard let payload = result?.notification.payload else { return }
guard let additionalData = payload.additionalData else { return }
guard let venueID = additionalData["internal"] as? String else { return }
DispatchQueue.main.async {
self.showVenueDetails(venueID)
}
},
settings: onesignalInitSettings)
OneSignal.inFocusDisplayType = .notification
OneSignal.promptForPushNotifications(userResponse: { accepted in
print("User accepted notifications: \(accepted)")
})
Yes, add kOSSettingsKeyInAppLaunchURL: false to your onesignalInitSettings. This will open URL in default browser instead of UIWebView.
If you want to display your custom view then don't use the URL parameter in the payload. Instead, use custom key-value pair in additional data.
For OneSignal SDK 3.x.x
As suggested by OneSignal, add the following key to the project's info.plist:
OneSignal_suppress_launch_urls = true
For OneSignal SDK version 2.x.x
add kOSSettingsKeyInAppLaunchURL: false to OneSignal's initSetting
Search for initOneSignal in project. There in setting parameneter pass
kOSSettingsKeyInAppLaunchURL: #false
- (void)initOneSignal {
[OneSignal setValue:#"react" forKey:#"mSDKType"];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didBeginObserving) name:#"didSetBridge" object:nil];
[OneSignal initWithLaunchOptions:nil appId:nil handleNotificationReceived:^(OSNotification* notification) {
[self handleRemoteNotificationReceived:[notification stringify]];
} handleNotificationAction:^(OSNotificationOpenedResult *result) {
if (!RCTOneSignal.sharedInstance.didStartObserving)
coldStartOSNotificationOpenedResult = result;
else
[self handleRemoteNotificationOpened:[result stringify]];
} settings:#{#"kOSSettingsKeyInOmitNoAppIdLogging" : #true, kOSSettingsKeyAutoPrompt : #false, kOSSettingsKeyInAppLaunchURL: #false}]; //default autoPrompt to false since init will be called again
didInitialize = false;
}
Related
I am trying to remove all support for Siri Shortcuts from my app. I have two types of shortcuts, both are made by donating INInteraction's.
func createDocIntent(for template: Document) -> CreateDocumentIntent {
let intent = CreateDocumentIntent()
intent.templateURI = template.URI.absoluteString
intent.templateTitle = template.title.trimmingCharacters(in: .whitespacesAndNewlines)
intent.suggestedInvocationPhrase = String(format: LS("siri-shortcut.create-document-suggested-phrase"), template.title.trimmingCharacters(in: .whitespacesAndNewlines))
return intent
}
func donateCreateDocShortcut(for template: Document) {
let intent = self.createDocIntent(for: template)
INInteraction(intent: intent, response: nil).donate { error in
if let error = error {
dprintln("Failed to donate CreateDocument shortcut to Siri with error: \(error.localizedDescription)")
}
}
}
To remove the shortcuts I have tried removing the Intent subclasses created in my Info.plist, and I have tried calling both INInteraction.deleteAll and NSUserActivity.deleteAllSavedUserActivities, but neither seem to actually delete the shortcuts (see below). Any shortcuts the user has made are still available in the Shorcuts.app, and can still be triggered by using voice commands.
INInteraction.deleteAll { (error) in
guard let error = error else { return }
print(error.localizedDescription)
}
NSUserActivity.deleteAllSavedUserActivities {
// Nothing to do
}
Is there something else I should be doing to remove existing Siri Shortcuts?
Is there a way to post a video or photo directly to the Instagram Stories of the user's Instagram account? For a normal photo share on Instagram, you can use the URL Scheme and open the Editor directly. Is there a way for Stories, too?
Thanks!
Swift 4.2 version of knshn's answer:
func shareBackgroundImage() {
let image = UIImage(imageLiteralResourceName: "backgroundImage")
if let pngImage = image.pngData() {
backgroundImage(pngImage, attributionURL: "http://your-deep-link-url")
}
}
func backgroundImage(_ backgroundImage: Data, attributionURL: String) {
// Verify app can open custom URL scheme, open if able
guard let urlScheme = URL(string: "instagram-stories://share"),
UIApplication.shared.canOpenURL(urlScheme) else {
// Handle older app versions or app not installed case
return
}
let pasteboardItems = [["com.instagram.sharedSticker.backgroundImage": backgroundImage,
"com.instagram.sharedSticker.contentURL": attributionURL]]
let pasteboardOptions: [UIPasteboard.OptionsKey: Any] = [.expirationDate: Date().addingTimeInterval(60 * 5)]
// This call is iOS 10+, can use 'setItems' depending on what versions you support
UIPasteboard.general.setItems(pasteboardItems, options: pasteboardOptions)
UIApplication.shared.open(urlScheme)
}
Instagram officially supports this since March 2018. https://developers.facebook.com/docs/instagram/sharing-to-stories/
For iOS:
You need to add instagram-stories to the LSApplicationQueriesSchemes key in your app's Info.plist.
This sample code shows how to pass the Instagram app a background layer image asset and an attribution deep link.
- (void)shareBackgroundImage {
[self backgroundImage:UIImagePNGRepresentation([UIImage imageNamed:#"backgroundImage"])
attributionURL:#"http://your-deep-link-url"];
}
- (void)backgroundImage:(NSData *)backgroundImage
attributionURL:(NSString *)attributionURL {
// Verify app can open custom URL scheme, open if able
NSURL *urlScheme = [NSURL URLWithString:#"instagram-stories://share"];
if ([[UIApplication sharedApplication] canOpenURL:urlScheme]) {
// Assign background image asset and attribution link URL to pasteboard
NSArray *pasteboardItems = #[#{#"com.instagram.sharedSticker.backgroundImage" : backgroundImage,
#"com.instagram.sharedSticker.contentURL" : attributionURL}];
NSDictionary *pasteboardOptions = #{UIPasteboardOptionExpirationDate : [[NSDate date] dateByAddingTimeInterval:60 * 5]};
// This call is iOS 10+, can use 'setItems' depending on what versions you support
[[UIPasteboard generalPasteboard] setItems:pasteboardItems options:pasteboardOptions];
[[UIApplication sharedApplication] openURL:urlScheme options:#{} completionHandler:nil];
} else {
// Handle older app versions or app not installed case
}
}
You can use this answer to convert a PHAsset's identifier into an asset URL, then format it into an instagram:// URL using this logic. That used to only be capable of making conventional Instagram posts, but as of the past few weeks it actually prompts users if they want to make a story or a post with the asset you provided on launch!
If you download .ipa of instagram and look at their Info.plist, we can found :
<key>CFBundleURLSchemes</key>
<array>
<string>instagram-stories</string>
<string>fb124024574287414</string>
<string>instagram</string>
<string>instagram-capture</string>
<string>fsq+kylm3gjcbtswk4rambrt4uyzq1dqcoc0n2hyjgcvbcbe54rj+post</string>
</array>
but of course it's private (not official) and not documented by Instagram. If anyone now how to use it/find parameters, I'm really curious to know !
For SwiftUI
import SwiftUI
fileprivate let wands: [Wand] = [
Wand(name: "Dumbledore's Wand", stickerAsset: "dumbledore"),
Wand(name: "Potter's Wand", stickerAsset: "potter"),
Wand(name: "Granger's Wand", stickerAsset: "granger")
]
struct ContentView: View {
func shareToInstagramStories(
stickerImage: String,
stickerLink: String,
backgroundTopColor: String = "#7F0909",
backgroundBottomColor: String = "#303030"
) {
// 1. Get a data object of our UIImage...
let stickerImageData = UIImage(named: stickerImage)?.pngData()
// 2. Verify if we are able to open instagram-stories URL schema.
// If we are able to, let's add our Sticker image to UIPasteboard.
let urlScheme = URL(string: "instagram-stories://share?source_application=\(Bundle.main.bundleIdentifier ?? "")")
if let urlScheme = urlScheme {
if UIApplication.shared.canOpenURL(urlScheme) {
var pasteboardItems: [[String : Any]]? = nil
if let stickerImageData = stickerImageData {
pasteboardItems = [
[
"com.instagram.sharedSticker.stickerImage": stickerImageData,
"com.instagram.sharedSticker.backgroundTopColor": backgroundTopColor,
"com.instagram.sharedSticker.backgroundBottomColor": backgroundBottomColor,
"com.instagram.sharedSticker.link": stickerLink
]
]
}
// We'll expire these pasteboard items in 5 minutes...
let pasteboardOptions = [
UIPasteboard.OptionsKey.expirationDate: Date().addingTimeInterval(60 * 5)
]
if let pasteboardItems = pasteboardItems {
UIPasteboard.general.setItems(pasteboardItems, options: pasteboardOptions)
}
// 3. Try opening the URL...
UIApplication.shared.open(urlScheme, options: [:], completionHandler: nil)
} else {
// App may not be installed. Handle those errors here...
print("Something went wrong. Maybe Instagram is not installed on this device?")
}
}
}
var body: some View {
NavigationView {
List(wands, id: \.name){ wand in
Text(wand.name).onTapGesture {
shareToInstagramStories(stickerImage: wand.stickerAsset, stickerLink: "www.google.com")
}
}
.navigationBarTitle(Text("Ollivanders"), displayMode: .inline)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Wand {
let name: String
let stickerAsset: String
}
Special thanks to [Ishan Chhabra][https://ishanchhabra.com/thoughts/sharing-to-instagram-stories]
I checked OneSignal documentation but I couldn't understand clearly as beginner how setting dictionary as a post notification's additional data (like postID, userID, type) in iOS Native SDK using Swift to decide and redirect when user interacted with notification.
For posting I'm doing only like that:
OneSignal.sendTag("username", value: "\(user)")
OneSignal.postNotification(["contents": ["en": "#\(user) added an additive to your '\(title)' experience: \"\(strLast)\""],
"include_player_ids": [postOwnerPlayerID],
For receiving:
OneSignal.initWithLaunchOptions(launchOptions, appId: "______", handleNotificationReceived: nil, handleNotificationAction: {
(result) in
// This block gets called when the user reacts to a notification received
let payload = result?.notification.payload
//Try to fetch the action selected
if let additionalData = payload?.additionalData {
print("payload")
print(additionalData)
}
// After deciding which action then I can redirect user..
let username: String? = UserDefaults.standard.string(forKey: KEY_UID)
if username != nil {
if let tabbarController = self.window!.rootViewController as? UITabBarController {
tabbarController.selectedViewController = tabbarController.viewControllers?[2]
// NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: "notificationsUp"), object: nil)
}
}
}, settings: [kOSSettingsKeyInFocusDisplayOption : OSNotificationDisplayType.none.rawValue])
You set the data field as a key in the dictionary passed to OneSignal.postNotification like the following.
OneSignal.postNotification(["contents": ["en": "Test Message"],
"include_player_ids": ["3009e210-3166-11e5-bc1b-db44eb02b120"],
"data": ["postID": "id"]])
Then you need to get ready your keys from additionalData from the payload in the handleNotificationAction function.
if let additionalData = payload?.additionalData {
let postID: String? = additionalData["postID"]
}
Example from iOS in objC to send additional data...
[OneSignal postNotification:#{#"contents":#{#"en":text},
#"include_player_ids":oneSignalIds,
#"data":#{#"key": #"value"},
}];
And to receive the data...
[OneSignal initWithLaunchOptions:launchOptions
appId:ONESIGNAL_APPID
handleNotificationReceived:^(OSNotification *notification) {
if (notification.payload.additionalData) {
NSDictionary* additionalData = notification.payload.additionalData;
if (additionalData[#"key"]){
NSLog(#"Received Data - %#", additionalData[#"key"]);
}
}
}
handleNotificationAction:nil
settings:#{kOSSettingsKeyInAppAlerts:#YES}];
Hope it helps someone :)
Thanks to #jkasten helped me in the right direction! helped me get rid of the AnyHashable warning I was getting.
Swift 3 code (change PATH to the additionalData parameter you want to output):
let PATH = notification!.payload.additionalData["PATH"]
print("PATH: ",PATH as Any)
If you're looking to do the same but in the Notification Service Extension, take a look at our updated documentation.
The Notification Service Extension is used for:
- Badges
- Influenced Opens with Firebase Analytics
- Media Attachments
- Action Buttons
We migrated from UA to One Signal. We are sending push from cloud code like
var pushInfo = {
"app_id" : "xxxxxx",
"data": {
"objectId": objectId,
"placeId": placeId,
},
"included_segments": ["All Users"],
"contents": {"en": message}
};
var headers = {
"Content-Type": "application/json; charset=utf-8",
"Authorization": "Basic XXXXX"
};
var options = {
host: "onesignal.com",
port: 443,
path: "/api/v1/notifications",
method: "POST",
headers: headers,
};
var https = require('https');
var req = https.request(options, function(res) {
res.on('data', function(data) {
console.log("Response:");
console.log(JSON.parse(data));
});
});
req.on('error', function(e) {
console.log("ERROR:");
console.log(e);
});
req.write(JSON.stringify(pushInfo));
req.end();
In my AppDelegate.m I do
[OneSignal initWithLaunchOptions:launchOptions appId:#"XXXXX"];
Now earlier when a notification is received and user Taps on it, it used to call
-(void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
Q. This is not getting called now. How do I handle it with OneSignal.
Q. What I need to do to handle a silent notification (no visible badge/banner etc)
I Assume you are testing/running your app on an iOS10 device,
I looked at OneSignal SDK Code and I think the SDK automatically uses the new UserNotifications Framework (add in iOS10), when iOS10 is detected on device.
In this case, the AppDelegate method you mentioned above does not get invoked, instead methods in UNUserNotificationCenterDelegate get invoked, which are captured by SDK to record clicks/views.
To Fix the issue, Create a new class which implements OSUserNotificationCenterDelegate and provide its instance to OneSignal using [OneSignal setNotificationCenterDelegate:yourCustomNotificationCenterDelegateInstance]
Please note that application:didReceiveRemoteNotification:fetchCompletionHandler: is still called when a silent push notification (content-available: 1) arrives, but its not called when user taps the notification if UNUserNotificationCenterDelegate is used.
Also, there was an issue on iOS 10.0.X where the application:didReceiveRemoteNotification was called instead of application:didReceiveRemoteNotification:fetchCompletionHandler: See: https://forums.developer.apple.com/thread/54332 , but I doubt if this is the case with you.
One Signal Notification Integration
Use Following Block Of Code TO Handle PushNotification Message Content
Put below code in AppDelegate's ApplicationDidFinishLaunch Options method
let notificationReceivedBlock: OSHandleNotificationReceivedBlock = { notification in
print("Received Notification: \(notification!.payload.notificationID)")
}
let notificationOpenedBlock: OSHandleNotificationActionBlock = { result in
// This block gets called when the user reacts to a notification received
let payload: OSNotificationPayload = result!.notification.payload
var fullMessage = payload.body
print("Message = \(String(describing: fullMessage))")
if payload.additionalData != nil {
if payload.title != nil {
let messageTitle = payload.title
print("payload.category \(payload.category)")
print("payload.subtitle \(payload.subtitle)")
print("Message Title = \(messageTitle!)")
}
let additionalData = payload.additionalData
if additionalData?["actionSelected"] != nil {
fullMessage = fullMessage! + "\nPressed ButtonID: \(String(describing: additionalData!["actionSelected"]))"
}
}
}
let onesignalInitSettings = [kOSSettingsKeyAutoPrompt: false,
kOSSettingsKeyInAppLaunchURL: true]
OneSignal.initWithLaunchOptions(launchOptions,
appId: "Your App Id",
handleNotificationReceived: notificationReceivedBlock,
handleNotificationAction: notificationOpenedBlock,
settings: onesignalInitSettings)
**Use following block of code To Receive OneSingnal Notification Content**
OneSignal.inFocusDisplayType = OSNotificationDisplayType.notification;
// Recommend moving the below line to prompt for push after informing the user about
// how your app will use them.
OneSignal.promptForPushNotifications(userResponse: { accepted in
print("User accepted notifications: \(accepted)")
})
application:didReceiveRemoteNotification:fetchCompletionHandler: is the correct selector for background silent content-available notifications. Make sure you are using the latest 2.2.2 OneSignal SDK as there were some fixes to maintain compatibility with the older AppDelegate selectors.
You may want to look into using UNNotificationServiceExtension with mutable-content for iOS 10 devices as this still works when the app has been swiped away.
In my case all I had to do is remove the Firebase handlers in app delegate. OneSignal can hijack the events from FCM. This way I had both the OneSingal Push notification and the ones delivered by Firebase. As for One signal I used a simple copy and paste from their examples.
THIS ARE THE PARTS THAT I REMOVED FROM MY AppDelegate.m
REMOVE IMPORTS -> #import "RNFirebaseMessaging.h"
REMOVE IMPORTS -> #import "RNFirebaseNotifications.h"
IN: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
REMOVE ->[RNFirebaseNotifications configure];
REMOVE HANDLERS:
- (void)application:(UIApplication *)application
didReceiveLocalNotification:(UILocalNotification *)notification {
[[RNFirebaseNotifications instance]
didReceiveLocalNotification:notification];
}
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo
fetchCompletionHandler:(nonnull void (^).
(UIBackgroundFetchResult))completionHandler{
[[RNFirebaseNotifications instance]
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
[UIApplication sharedApplication].applicationIconBadgeNumber += 1;
}
- (void)application:(UIApplication *)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings
*)notificationSettings {
[[RNFirebaseMessaging instance]
didRegisterUserNotificationSettings:notificationSettings];
}
import { Platform } from 'react-native'
import OneSignal, { NotificationReceivedEvent, OpenedEvent } from 'react-native-onesignal'
import Config from '../../Config/Config'
import State from '../../State/State'
interface WithSuccess {
success: boolean
}
interface ExternalUserIdResultI {
push: WithSuccess
email: WithSuccess
sms: WithSuccess
}
class Messaging {
public Init = () => {
//OneSignal Init Code
OneSignal.setLogLevel(Config.messaging.debugLevel, 0)
OneSignal.setAppId(Platform.OS === 'ios' ? Config.messaging.iosAppId : Config.messaging.androidAppId)
//END OneSignal Init Code
//Prompt for push on iOS
OneSignal.promptForPushNotificationsWithUserResponse((response: boolean) => {
console.log("Prompt response:", response);
})
//Method for handling notifications received while app in foreground
OneSignal.setNotificationWillShowInForegroundHandler((notificationReceivedEvent: NotificationReceivedEvent) => {
console.log("OneSignal: notification will show in foreground:", notificationReceivedEvent);
let notification = notificationReceivedEvent.getNotification();
console.log("notification: ", notification);
notificationReceivedEvent.complete(notification)
})
//Method for handling notifications opened
OneSignal.setNotificationOpenedHandler((notification: OpenedEvent) => {
console.log("OneSignal: notification opened:", notification);
})
OneSignal.addSubscriptionObserver(event => {
console.log("OneSignal: subscription changed:", event);
})
}
public SendTag = (key: string, value: string) => {
OneSignal.sendTag(key, value)
}
public SetExternalUserId = (externalUserId: string) => {
//#ts-ignore
OneSignal.setExternalUserId(externalUserId, (results: ExternalUserIdResultI) => {
// The results will contain push and email success statuses
console.log('Results of setting external user id');
console.log(results);
// Push can be expected in almost every situation with a success status, but
// as a pre-caution its good to verify it exists
if (results.push && results.push.success) {
console.log('Results of setting external user id push status:');
console.log(results.push.success);
}
// Verify the email is set or check that the results have an email success status
if (results.email && results.email.success) {
console.log('Results of setting external user id email status:');
console.log(results.email.success);
}
// Verify the number is set or check that the results have an sms success status
if (results.sms && results.sms.success) {
console.log('Results of setting external user id sms status:');
console.log(results.sms.success);
}
});
}
public RemoveExternalUserId = () => {
//#ts-ignore
OneSignal.removeExternalUserId((results: ExternalUserIdResultI) => {
// The results will contain push and email success statuses
console.log('Results of removing external user id');
console.log(results);
// Push can be expected in almost every situation with a success status, but
// as a pre-caution its good to verify it exists
if (results.push && results.push.success) {
console.log('Results of removing external user id push status:');
console.log(results.push.success);
}
// Verify the email is set or check that the results have an email success status
if (results.email && results.email.success) {
console.log('Results of removoing external user id email status:');
console.log(results.email.success);
}
});
}
}
export default new Messaging()
I want to send tag to a specific user after he/she logged in so he/she can receive notifications. Only logged in users will receive notifications.
When he/she logs out, I will delete his/her tag.
How can I do this?
My code in AppDelegate:
let oneSignal: OneSignal = OneSignal(launchOptions: launchOptions, appId: "<my-app-id>") {
(message, additionalData, isActive) in
if (additionalData != nil) {
NSLog("APP LOG ADDITIONALDATA: %#", additionalData);
let displayMessage: NSString = NSString(format:"NotificationMessage:%#", message);
var messageTitle: NSString = "";
if (additionalData["discount"] != nil) {
messageTitle = additionalData["discount"] as String
}
else if (additionalData["bonusCredits"] != nil) {
messageTitle = additionalData["bonusCredits"] as String;
}
else if (additionalData["actionSelected"] != nil) {
messageTitle = NSString(format:"Pressed ButtonId:%#", additionalData["actionSelected"] as String);
}
var alertView: UIAlertView = UIAlertView(title: messageTitle,
message:displayMessage,
delegate:self,
cancelButtonTitle:"Close");
alertView.show();
}
else if (isActive) {
var alertView: UIAlertView = UIAlertView(title:"OneSignal Message",
message:message,
delegate:self,
cancelButtonTitle:"Close");
alertView.show();
}
}
My code in my LogInViewController:
let oneSignal = OneSignal()
oneSignal.sendTag("username", value: self.usernameTextField.text)
The code in my appDelegate is working fine, my users already receive notifications. But they can receive notifications even if they're not logged in.
You need to use the same oneSignal instance from AppDelegate in your LogInViewController. You can make oneSignal static at the class level so it can be shared between both classes.
To delete a tag you can call oneSignal.deleteTag("username")
Update:
As of the iOS 2.0 SDK all methods on the OneSignal class are now static.
Objective-C:
// Send tag: After login
[OneSignal sendTag:#"key" value:#"value"];
// Delete tag: After logout
[OneSignal deleteTag:#"key"];
Swift:
// Send tag: After login
OneSignal.sendTag("key", value: "value") // for sending that is inserting tag in OneSignal
// Delete tag: After logout
OneSignal.deleteTag("key") // delete that specific tag from OneSignal db