Call from App Extension (iMessage) - ios

I'm working on iMessage app extension for my app and i was wondering if its possible to initiate a phone call from App Extension?
I tried using the following code but it takes me (deeplink) to containing app.
cell.didTapCallNowButton = { cell in
if let phoneNumber = cell.Model.phone,
let url = URL(string: "telprompt:\(phone)") {
self.extensionContext?.open(url, completionHandler: nil)
}
}

Here is the answer from pdm 'Apple Staff' on Apple developer forum:
No. iMessage apps can only open URLs in their parent app.
https://forums.developer.apple.com/message/174112#174112

Related

How to send a message (tokens) from an iOS App to its Safari Extension?

I tried looking for a solution in posts such as this and this where people had a very similar problem: How to send a message from iOS App to a Safari Extension?
I even read this article where the author was explaining how to use SafariExtensionHandler to send a message from the browser to the app and back to the browser after selecting the context menu, but it's not quite what I was looking for.
Sending a Token from iOS App to Safari Extension
In the app, the user has to enter an email and password to log into their account. Once they log in, I save their information in UserDefaults like this:
class AuthDataService {
{...}
URLSession.shared.dataTaskPublisher(for: urlRequest)
.tryMap { data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200,
let accessToken = httpResponse.value(forHTTPHeaderField: "Access-Token"),
let clientId = httpResponse.value(forHTTPHeaderField: "Client"),
let uid = httpResponse.value(forHTTPHeaderField: "Uid")
else {
throw CustomError.cannotExecuteRequest
}
let sharedDefaults = UserDefaults(suiteName: "group.com.MyCompany.MyProject")
sharedDefaults?.set(accessToken, forKey: "Access-Token")
sharedDefaults?.set(clientId, forKey: "Client")
sharedDefaults?.set(uid, forKey: "Uid")
return data
}
{...}
}
App-Group
From my understanding of this article, I need to create an App Group, in order to share the data between the iOS App and the Safari Extension. I named the group: "group.com.MyCompany.MyProject" (just like the suiteName in UserDefaults).
Home View
The screen that the user sees when they log in, is a SwiftUI View that has a Link which takes the user to Safari so they can open the extension themselves:
struct HomeView: View {
#EnvironmentObject var viewModel: AuthViewModel
var body: some View {
Link(destination: URL(string: "https://www.apple.com/")!) {
Text("Take me to Safari")
}
}
}
SafariWebExtensionHandler
Now, all the articles that I read were talking about how to send data from the Safari Extension to the iOS app through SafariWebExtensionHandler's beginRequest(with:).
However, I'm trying to send the Tokens in UserDefaults either whenever the user logs in the app, or when they open the Safari Extension.
I tried retrieving the data from UserDefaults to see if I could at least read it in the terminal, but the debugger never gets to the print statements:
import SafariServices
import os.log
let SFExtensionMessageKey = "message"
class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
func readData() {
let sharedDefaults = UserDefaults(suiteName: "group.com.lever.clientTokens")
print(sharedDefaults?.object(forKey: "Access-Token")) //<-- This line never gets executed
}
func beginRequest(with context: NSExtensionContext) {
let item = context.inputItems[0] as! NSExtensionItem
let message = item.userInfo?[SFExtensionMessageKey]
os_log(.default, "Received message from browser.runtime.sendNativeMessage: %#", message as! CVarArg)
let response = NSExtensionItem()
response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ]
readData()
context.completeRequest(returningItems: [response], completionHandler: nil)
}
}
Question
macOS vs iOS
This documentation from Apple has a section called Send messages from the app to JavaScript which is pretty much what I want to do. The documentation even mentions SFSafariApplication.dispatchMessage(withName:toExtensionWithIdentifier:userInfo:completionHandler:) which in theory sends a message to the JavaScript script, but it says it only works in macOS:
You can’t send messages from a containing iOS app to your web
extension’s JavaScript scripts.
This excellent Medium article talks about sending an APIKey from the app to the Safari Extension using an API from openai.com. It seems that it also uses SFSafariApplication to communicate with SafariWebExtensionHandler, but again it looks like it only works for macOS.
Safari Extension to webPage
I also read this other Apple documentation thinking it would help, but it only talks about passing messages from the Safari Extension's popup to the webpage.
Conclusion
So my question is:
Is writing code in SafariWebExtensionHandler the right way to send data from the iOS App to my Safari Extension? Can this be done in iOS? Or is it only available for macOS?
I read some other articles that were talking about using the JavaScript files in the Resources folder in order to "listen" to changes. But I'm a little confused as to how I can send those changes from my App in order for the Safari Extension to listen to them.
What I am trying to achieve is for the user to be already logged-in in the Safari Extension after they are redirected from the HomeView in the iOS App, instead of having to sign in another time.
Thank you for your time and help!

Force user to update the app programmatically in iOS

In my iOS app I have enabled force app update feature. It is like this.
If there is a critical bug fix. In the server we are setting the new release version. And in splash screen I am checking the current app version and if its lower than the service version, shows a message to update the app.
I have put 2 buttons "Update now", "Update later"
I have 2 questions
If I click now. App should open my app in the appstore with the button UPDATE. Currently I use the link "http://appstore.com/mycompanynamepvtltd"
This opens list of my company apps but it has the button OPEN, not the UPDATE even there is a new update for my app. whats the url to go for update page?
If he click the button "Update Later" is it ok to close the app programmatically? Does this cause to reject my app in the appstore?
Please help me for these 2 questions
Point 2 : You should only allow force update as an option if you don't want user to update later. Closing the app programmatically is not the right option.
Point 1 : You can use a good library available for this purpose.
Usage in Swift:
Library
func applicationDidBecomeActive(application: UIApplication) {
/* Perform daily (.daily) or weekly (.weekly) checks for new version of your app.
Useful if user returns to your app from the background after extended period of time.
Place in applicationDidBecomeActive(_:)*/
Siren.shared.checkVersion(checkType: .daily)
}
Usage in Objective-C: Library
-(void)applicationDidBecomeActive:(UIApplication *)application {
// Perform daily check for new version of your app
[[Harpy sharedInstance] checkVersionDaily];
}
How it works : It used lookup api which returns app details like link including version and compares it.
For an example, look up Yelp Software application by iTunes ID by calling https://itunes.apple.com/lookup?id=284910350
For more info, please visit link
Don't close the app programmatically. Apple can reject the app. Better approach will be do not allow user to use the app. Keep the update button. Either user will go to app store or close the app by himself.
According to Apple, your app should not terminate on its own. Since the user did not hit the Home button, any return to the Home screen gives the user the impression that your app crashed. This is confusing, non-standard behavior and should be avoided.
Please check this forum:
https://forums.developer.apple.com/thread/52767.
It is happening with lot of people. In my project I redirected the user to our website page of downloading app from app store. In that way if the user is not getting update button in app store, at least the user can use the website in safari for the time being.
To specifically answer your question:
Use this URL to directly open to your app in the app store:
https://apps.apple.com/app/id########## where ########## is your app's 10 digit numeric ID. You can find that ID in App Store Connect under the App Information section. It's called "Apple ID".
I actually have terminate functionality built into my app if it becomes so out of date that it can no longer act on the data it receives from the server (my app is an information app that requires connectivity to my web service). My app has not been rejected for having this functionality after a dozen updates over a couple years, although that function has never been invoked. I will be switching to a static message instead of terminating the app, just to be safe to avoid future updates from being rejected.
I have found that the review process is at least somewhat subjective, and different reviewers may focus on different things and reject over something that has previously been overlooked many times.
func appUpdateAvailable() -> (Bool,String?) {
guard let info = Bundle.main.infoDictionary,
let identifier = info["CFBundleIdentifier"] as? String else {
return (false,nil)
}
// let storeInfoURL: String = "http://itunes.apple.com/lookupbundleId=\(identifier)&country=IN"
let storeInfoURL:String = "https://itunes.apple.com/IN/lookup?
bundleId=\(identifier)"
var upgradeAvailable = false
var versionAvailable = ""
// Get the main bundle of the app so that we can determine the app's
version number
let bundle = Bundle.main
if let infoDictionary = bundle.infoDictionary {
// The URL for this app on the iTunes store uses the Apple ID
for the This never changes, so it is a constant
let urlOnAppStore = NSURL(string: storeInfoURL)
if let dataInJSON = NSData(contentsOf: urlOnAppStore! as URL) {
// Try to deserialize the JSON that we got
if let dict: NSDictionary = try?
JSONSerialization.jsonObject(with: dataInJSON as Data, options:
JSONSerialization.ReadingOptions.allowFragments) as! [String:
AnyObject] as NSDictionary? {
if let results:NSArray = dict["results"] as? NSArray {
if let version = (results[0] as! [String:Any]).
["version"] as? String {
// Get the version number of the current version
installed on device
if let currentVersion =
infoDictionary["CFBundleShortVersionString"] as? String {
// Check if they are the same. If not, an
upgrade is available.
print("\(version)")
if version != currentVersion {
upgradeAvailable = true
versionAvailable = version
}
}
}
}
}
}
}
return (upgradeAvailable,versionAvailable)
}
func checkAppVersion(controller: UIViewController){
let appVersion = ForceUpdateAppVersion.shared.appUpdateAvailable()
if appVersion.0 {
alertController(controller: controller, title: "New Update", message: "New version \(appVersion.1 ?? "") is available")
}
}
func alertController(controller:UIViewController,title: String,message: String){
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Update", style: .default, handler: { alert in
guard let url = URL(string: "itms-apps://itunes.apple.com/app/ewap/id1536714073") else { return }
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}))
DispatchQueue.main.async {
controller.present(alertController, animated: true)
}
}
Use appgrades.io. Keep your app focus on delivering the business value and let 3rd party solution do their tricks. With appgrades, you can, once SDK integrated, create a custom view/alert to display for your old versions users asking them to update their apps. You can customize everything in the restriction view/alert to make it appear as part of your app.

Add function for cancel in UIAlert of Phone Call function

I have a function that places a phone call when a button is pressed.
private func callNumber(phoneNumber:String) {
if let phoneCallURL = URL(string: "tel://\(phoneNumber)") {
let application:UIApplication = UIApplication.shared
if (application.canOpenURL(phoneCallURL)) {
application.open(phoneCallURL, options: [:], completionHandler: nil)
}
}
}
I want to add some functionality if the users hit cancel when the alert pops up to confirm the call. How would I do that?
According to this question: Prompt when trying to dial a phone number using tel:// scheme on iOS 10.3
This alert is actually a bug in iOS 10.3 and should be removed at some point in the future. It isn't supposed to come up for "tel:" links in native apps.
That said, I don't believe there is a way to detect the alert and how the user interacts with it.

Is it okay to download data directly to watch OS from server

So I'm trying to make a watchOS app for a music streaming app, and I found an example pretty much close to what I'm going to make.
(https://github.com/belm/BaiduFM-Swift)
But It seems like the project is kinda outdated. According to the codes below, watch extension is getting required datas like sound, images via HttpRequest. From what I read, watchOS 3 supports Background Connectivity, (which enables app to transfer data more efficiently) and Apple encourages developers to process and get data from the main app.
What is right way to do it? Is there any good example to see?
// play song method in interface controller
HttpRequest.getSongLink(info.id, callback: {(link:SongLink?) -> Void in
if let songLink = link {
DataManager.shareDataManager.curSongLink = songLink
DataManager.shareDataManager.mp.stop()
var songUrl = Common.getCanPlaySongUrl(songLink.songLink)
DataManager.shareDataManager.mp.contentURL = NSURL(string: songUrl)
DataManager.shareDataManager.mp.prepareToPlay()
DataManager.shareDataManager.mp.play()
DataManager.shareDataManager.curPlayStatus = 1
Async.main{
self.songTimeLabel.setText(Common.getMinuteDisplay(songLink.time))
}
HttpRequest.getLrc(songLink.lrcLink, callback: { lrc -> Void in
if let songLrc = lrc {
DataManager.shareDataManager.curLrcInfo = Common.praseSongLrc(songLrc)
//println(songLrc)
}
})
}
})

In-app Purchases from iMessage app extension

I created an app that uses in-app purchases, I already have set up the in-app purchases on iTunes and I even tested it using a sandbox user and all of this is working perfectly.
now that I included an iMessage app extension, the same in-app purchase products return invalidProduct Id, but continues working well inside the main app.
could be because the bundle id?
mainapp: com.whatever.bundleid
app iMessage: com.whatever.bundleid.iMessage
iMessage extension: com.whatever.bundleid.iMessage.MessagesExtension
btw, i'm using the SwiftyStoreKit pod.
if let inappIndentifier = stickerPackage.inAppPurchaseId {
SwiftyStoreKit.retrieveProductsInfo([inappIndentifier]) { result in
if let product = result.retrievedProducts.first {
let priceString = product.localizedPrice!
completion(true, priceString)
} else if let _ = result.invalidProductIDs.first {
completion(false, nil)
} else {
print("Error: \(result.error)")
completion(false, nil)
}
}
}
I made a huge mistake, instead of creating an iMessage extension, I've created an iMessage app extension.
Once I removed the "iMessage app" the in-app purchases started working correctly.

Resources