Is it possible to change NSUserActivity's userInfo from another app? - ios

I am trying to open app B, from app A using universal links as below:
#IBAction func openButtonPressed(_ sender: Any) {
if let appURL = URL(string: "https://website.com/section/Paris") {
UIApplication.shared.open(appURL) { success in
if success {
print("The URL was delivered successfully.")
} else {
print("The URL failed to open.")
}
}
} else {
print("Invalid URL specified.")
}
}
In App B's AppDelegate -> application continueUserActivity restorationHandler method, it calls another function where the userActivity is processed.
Here there is a check for NSUserActivity's userInfo.
It is checking for a key PageName in NSUserActivity's userInfo property, and according to the value it assigns a type to the activity and routes the app accordingly.
NSString *page = activity.userInfo[#"PageName"];
if ([page isEqualToString:#"Places"]) {
return UserActivityTypePlaces;
} else {
return UserActivityTypeLink;
}
I was wondering if I can change or add a key value pair to userInfo in App A, so that App B can route me to another tab. (In this case I want it to open another tab and search the location string that is in the universal link)
I have checked other questions related with NSUserActivity's userInfo on StackOverflow and Apple Developer forums but they are not changing it on the calling app.(such as in App A)
Thank you

I made these in App A:
let activity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
activity.title = "Places"
activity.userInfo = ["Page" : "Places"]
let userInfoSet:Set = ["Page"]
activity.requiredUserInfoKeys = userInfoSet
self.userActivity = activity
self.userActivity?.becomeCurrent()
and added the NSUserActivityDelegate method:
override func updateUserActivityState(_ activity: NSUserActivity) {
let dict:Dictionary = ["Page":"Places"]
userActivity?.addUserInfoEntries(from: dict)
}
But the NSUserActivity object is not something that goes from one app to the other. So this change only works within app A.
Hope this helps someone else too.

Related

iOS ShareContext tapping on Suggestion Intent property of extensionContext is nil

I have a ShareExtension in my iOS app. I am trying to use Suggestions. I can successfully 'donate' the intent using the following code from the apple developer website:
let groupName = INSpeakableString(spokenPhrase: "Juan Chavez")
let sendMessageIntent = INSendMessageIntent(recipients: nil,
content: nil,
speakableGroupName: groupName,
conversationIdentifier: "sampleConversationIdentifier",
serviceName: nil,
sender: nil)
// Add the user's avatar to the intent.
let image = INImage(named: "Juan Chavez")
sendMessageIntent.setImage(image, forParameterNamed: \.speakableGroupName)
// Donate the intent.
let interaction = INInteraction(intent: sendMessageIntent, response: nil)
interaction.donate(completion: { error in
if error != nil {
// Add error handling here.
} else {
// Do something, e.g. send the content to a contact.
}
})
This works fine and I am able to see my app icon in the suggestion row on the top for each conversation. However when I click on the suggestion, the intent property of the extentsionContext is nil:
override func viewDidLoad() {
super.viewDidLoad()
// Populate the recipient property with the metadata in case the user tapped a suggestion from the share sheet.
let intent = self.extensionContext?.intent as? INSendMessageIntent
if intent != nil { // this is nil despite selecting suggestion
let conversationIdentifier = intent!.conversationIdentifier
self.recipient = recipient(identifier: conversationIdentifier!)
}
}
My ShareExtension plist is as follows:
The other odd behaviour is that I'm able to do the donate from the main app but not from the app extension. In the main app the only relevant entry in the plist is the same NSUserActivityTypes entry. Not the NSExtension entries.
My understanding is that tapping on the suggestion, the extensionContext should contain the intent.

Launching an app with a url in swift then calling an API before launching the app

This is a bit of a tough one and I apologize if the title is incorrect. I wasn't sure how to word the title to have it make sense.
Essentially I am using the Jitsi SDK in iOS and we have it setup to use JWT for authentication and identifying host/guest. My problem comes in when the app is launched with a URL. The method...
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {...
When that is launched it has the room number as part of the URL. That all makes sense. My problem is I need to call an API to "Checkin" the user and retrieve the jwt that is generated on the server. The above function has a return on whether or not the app should launch and all that jazz and in the Jitsi documentation it shows the return should be...
return JitsiMeet.sharedInstance().application(app, open: finalURL, options: options)
However I don't want it to do that. I need to make an API call, the callback of my api call will have the jwt that I need, then I want it to open the app normally so I can handle joining the conference on my own.
I guess my main question is.. If I make an API call that has a callback that will launch the application with the needed arguments, but then I just return false from that function, will it work correctly?
I know that may sound confusing, so here is a quick snippet of what I was thinking...
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
guard NSURLComponents(url: url resolvingAgainstBaseURL: true) != nil else {
return false
}
let room = url.lastPathComponent
if let finalURL = URL(string: 'myserverhere/' + room) {
callCheckinAPI(room: room) { (res) in
if(res.success) {
DispatchQueue.main.async {
self.launchMainApp(room)
}
} else {
//not sure how to return false from here
}
}
return false
}
return false
}
I feel like that would be a problem because the function would get its return before the Api call is completed, since the return can't wait for the api call to finish, but I'm not sure how I could go about handling this scenario. That is the method that gets called in AppDelegate when the app is launched with a specific URL, which is what happens when a user clicks on the link to join the meeting. So, is there a way to make an API call if the app is launched with a specific URL, and then handle it accordingly? Am I on the right path and the above stuff should, theoretically, work? I am just kind of lost and need the advice of someone who is much better with swift than I am.
Thank you for reading through, and I apologize again if the title is not correct. It was hard to explain and I wasn't sure what the title should be.
When application(open,options) is called you have to return a bool right away. If you make an asynchronous call to the server for the JWT token and then return false it'll just stop right there.
So just return true and proceed with what you're doing. After looking at the documentation https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application
it seems like you're on your way to launching the app anyways if it's not already launched.
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
guard NSURLComponents(url: url resolvingAgainstBaseURL: true) != nil else {
return false
}
let room = url.lastPathComponent
// new made up function
guard roomExists(url) else {
return false
}
if let finalURL = URL(string: 'myserverhere/' + room) {
callCheckinAPI(room: room) { (res) in
if(res.success) {
DispatchQueue.main.async {
// the view controller should already be attached to the window by this point
// so inside this function it should have a function to connect to the room
mainviewcontroller.connect(to:room)
// self.launchMainApp(room)
}
} else {
// show alert that something went wrong
}
}
}
return true
}

Firebase dynamic link short url returns nil ios

I am facing an issue with Firebase dynamic link. The link is generated from the backend. and it's working perfectly in android. but in iOS when I shared the dynamic link on whatsapp or messages app on iPhone. and tap on the link app is opened. and our following function is called in Appdelegate. and after that firebase function is called with the received URL. and the firebase functions return nil. But when I open that link from the safari app is opened and that same firebase function returns the deeplink URL. I have searched alot but did not find the solution. I am using firebase 7.11 version pods.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let incomingUrl = userActivity.webpageURL{
print("Incoming URL is \(incomingUrl)")
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingUrl) { (dynamicLink, error) in
// guard error == nil else{
// print("Found an error! \(error!.localizedDescription)")
// return
// }
print("error \(error)")
print("dynamicLink \(dynamicLink)") // It returns nil when app open from whatsapp or any 3rd party app.
}
if linkHandled{
return true
}else{
return false
}
}
return false
}
I have also received the following error in both cases.
Error Domain=com.firebase.dynamicLinks Code=403 "(null)" UserInfo={code=403, message=Requests to this API firebasedynamiclinks.googleapis.com method google.firebase.dynamiclinks.v1.DynamicLinksService.GetIosReopenAttribution are blocked., status=PERMISSION_DENIED, details=(
{
"#type" = "type.googleapis.com/google.rpc.ErrorInfo";
domain = "googleapis.com";
metadata = {
consumer = "projects/37866531840";
service = "firebasedynamiclinks.googleapis.com";
};
reason = "API_KEY_SERVICE_BLOCKED";
}
)}
Did you add your custom domains in your info.plist file with FirebaseDynamicLinksCustomDomains parameter like image below.
Have you set AppBundle id as deepLinkURLScheme as like below in AppDelegate class under function didFinishLaunchingWithOptions
let kAppBundleID = Bundle.main.bundleIdentifier
FirebaseOptions.defaultOptions()?.deepLinkURLScheme = kAppBundleID
Please try with this.
Reference:
https://firebase.google.com/docs/dynamic-links/ios/create
By default, Dynamic Links uses your app's bundle identifier as the URL scheme needed to open up your application. We recommend staying with this default value to keep your implementation simple.

Universal Links: continueUserActivity not working when app is not in background

I manage to do universal links and able to redirect it from continueUserActivity but the problem now is continueUserActivity will not work if the app is not in background. which means if you close the app and click the link it won't open.
I have found a code, but i don't know how to get the url. here is the solution
let activityDic = launchOptions?[UIApplicationLaunchOptionsUserActivityDictionaryKey]
if let isActivityDic = activityDic {
// Continue activity here
}
because when i check the type data of isActivityDic , it's an any object data. please help me to get the url
and this code also throw error
if let activityDic = launchOptions?[UIApplicationLaunchOptionsUserActivityDictionaryKey] {
let options = activityDic.allValues.filter({ (option:AnyObject) -> Bool in
return option is NSUserActivity
})
if options.count > 0 , let userActivity = options[0] as? NSUserActivity{
// User activity handling should be done here
}
}

AdReward from AdColony not working on Swift 2.0 for some reason

I would like to implement a reward interstitial in my game, but i'm getting a lot of AdColony errors such as: "No fill for ad request" or that my Zone ID is invalid.
To start of, this would be how I configured my AdColony Zone:
Zone is active? Yes
Zone Type: Preroll/Interstitial (Gives me "No fill for ad request error")
Value Exchange/V4VC (Gives me "Zone ID invalid, please check config error")
House Ads: Back Fill
Options: 0 0 1
Development: Show Test Ads Only (Although my app is currently Live)
The example they give you with the SDK download, is for Apps not for Games so I tried to kinda translate it for Games, although it wasn't that different, but there might be a problem with my current code.
So this is how I have it in my GameViewController.swift.
// Outside I declare a struct
struct Constants
{
static let adColonyAppID = "app5aab6bb6aaf3xxxxxx"
static let adColonyZoneID = "vz19959f95bd62xxxxxx"
static let currencyBalance = "coinAmount"
}
// Inside GameViewController
var ad: AdColonyInterstitial?!
var spinner: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
self.setupAdRewardBanner()
}
func setupAdRewardBanner() {
AdColony.configureWithAppID(Constants.adColonyAppID, zoneIDs: [Constants.adColonyZoneID], options: nil,
completion: {(zones) in
let zone = zones.first
zone?.setReward({ (success, name, amount) in
if (success) {
let storage = NSUserDefaults.standardUserDefaults()
let wrappedBalance = storage.objectForKey(Constants.currencyBalance)
var balance = 0
if let nonNilNumWrappedBalance = wrappedBalance as? NSNumber {
balance = Int(nonNilNumWrappedBalance.integerValue)
}
balance = balance + Int(amount)
let newBalance: NSNumber = NSNumber(integerLiteral: balance)
storage.setValue(newBalance, forKey: Constants.currencyBalance)
storage.synchronize()
self.updateCurrencyBalance()
}
})
//If the application has been inactive for a while, our ad might have expired so let's add a check for a nil ad object
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.onBecameActive), name: "onBecameActive", object: nil)
//AdColony has finished configuring, so let's request an interstitial ad
self.requestInterstitial()
})
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.triggerAdReward), name: "triggerAdReward", object: nil)
self.updateCurrencyBalance()
}
func requestInterstitial()
{
//Request an interstitial ad from AdColony
AdColony.requestInterstitialInZone(Constants.adColonyZoneID, options:nil,
//Handler for successful ad requests
success:{(newAd) in
//Once the ad has finished, set the loading state and request a new interstitial
newAd.setClose({
self.requestInterstitial()
})
//Interstitials can expire, so we need to handle that event also
newAd.setExpire( {
self.ad = nil
self.requestInterstitial()
})
//Store a reference to the returned interstitial object
self.ad = newAd
},
//Handler for failed ad requests
failure:{(error) in
NSLog("SAMPLE_APP: Request failed with error: " + error.localizedDescription + " and suggestion: " + error.localizedRecoverySuggestion!)
}
)
}
func triggerAdReward(sender: AnyObject)
{
if let ad = self.ad {
if (!ad!.expired) {
ad?.showWithPresentingViewController(self)
}
}
}
func updateCurrencyBalance()
{
//Get currency balance from persistent storage and display it
let storage = NSUserDefaults.standardUserDefaults()
let wrappedBalance = storage.objectForKey(Constants.currencyBalance)
var balance: Int = 0
if let nonNilNumWrappedBalance = wrappedBalance as? NSNumber {
balance = Int(nonNilNumWrappedBalance.integerValue)
}
print("current balance ", balance)
//XXX Run animation of giving user coins and update view
}
func onBecameActive()
{
//If our ad has expired, request a new interstitial
if (self.ad == nil) {
self.requestInterstitial()
}
}
And then after all that, I call this notification to request the ad interstitial when pressing a button after the user loses in GameScene.
NSNotificationCenter.defaultCenter().postNotificationName("triggerAdReward", object: nil)
I tried debugging, I can't seem to see the code getting inside the if (success) block. So there might be an issue there.
Does anyone know what I'm doing wrong?
After debugging more, i noticed that it's not advancing with this method
self.requestInterstitial()
So there might be an issue with my account maybe? Why is not passing through the success and goes through the error block?
The error in the console is :
SAMPLE_APP: Request failed with error: No fill for ad request and
suggestion: Make sure you have configured your zone properly in the
control panel: http://clients.adcolony.com.
Thanks in advance.
It seems your code should be working.
Since you want to implement a reward interstitial, you should set the zone type to V4VC.
In case it said "Zone ID invalid, please check config error", you should check twice the App id and zone id in the source code and the Adcolony client panel page.
After you changed the zone type, wait for some time(10 min?) to test, the server should need time to sync the status.
Test on device instead of simulator if possible.
Here is the document for v4vc: https://github.com/AdColony/AdColony-iOS-SDK-3/wiki/Rewarded-Interstitial-Ads-Quick-Start-Guide

Resources