I'm working on a project in which, I have UIWebView where I need to call some JavaScripts on a webpage in this UIWebView and that particular JavaScript will call my Objective-C native method.
To do that, I'm fetching context from UIWebView and setting my Objective-C object to the context and this object I'm fetching in JavaScriptand In JavaScript, object I'm calling a Objective-c with this function/method.
Here is the code I'm using to do above,
JSContext *context = nil;
context = [_webView valueForKeyPath:#"documentView.webView.mainFrame.javaScriptContext"];
// enable error logging
[context setExceptionHandler:^(JSContext *context, JSValue *value) {
NSLog(#"WEB JS: %#", value);
}];
context[#"MyObj"] = self;
So my simple question is, whether this way of doing this is ok, in terms of Apple Store. I mean, is there something that can cause my app to get rejected my Apple for App Store?
Thanks.
There is nothing currently in the App Store guidelines that would prohibit what you intend to do.
The ability to initiate functionality from a web server, even if it's via JavaScript in a web view, is common behavior.
Your app is run in a sandbox, so any security concerns are limited to your app. You're not adding code to your app, which is strictly prohibited. You're simply including functionality that may be called at a later time.
The short answer is that it depends on who reviews your app.
I have apps that do something very similar, and they were approved without issue. I have other apps rejected for doing the same thing because the JavaScript was considered "downloaded executable code".
To answer your specific question, I do not believe what you are doing in your Objective-C code will cause a problem with Apple's review, but depending on the source of the JavaScript, that may.
Bottom line is that Apple's review guidelines are still interpreted by humans at Apple, and that interpretation is not perfectly consistent.
Related
I am implementing firebase dynamic links in my iOS app and I can already parse the link, redirect to AppStore etc. Now I want to distinguish the first run of the app, when user installs it from the dynamic link - I want to skip the intro and show him the content that is expected to be shown.
Is there some parameter, that I could catch in application(_:didFinishLaunchingWithOptions:) so I could say that it was launched thru the dynamic link?
The method application(_:continueUserActivity:userActivity:restorationHandler:) is called later, so the intro is already launched.
This case is difficult to test, because you have to have your app published on the AppStore.
You actually don't need to have the app published in the App Store for this to work — clicking a link, closing the App Store, and then installing an app build through Xcode (or any other beta distribution platform like TestFlight or Fabric) has exactly the same effect.
According to the Firebase docs, the method that is called for the first install is openURL (no, this makes no sense to me either). The continueUserActivity method is for Universal Links, and is only used if the app is already installed when a link is opened.
I am not aware of any way to detect when the app is opening for the first time after install from a 'deferred' link, but you could simply route directly to the shared content (skipping the intro) whenever a deep link is present. If a deep link is NOT present, show the regular intro.
Alternative Option
You could check out Branch.io (full disclosure: I'm on the Branch team). Amongst other things, Branch is a great, free drop-in replacement for Firebase Dynamic Links with a ton of additional functionality. Here is an example of all the parameters Branch returns immediately in didFinishLaunchingWithOptions:
{
"branch_view_enabled" = 0;
"browser_fingerprint_id" = "<null>";
data = "{
\"+is_first_session\":false,
\"+clicked_branch_link\":true,
\"+match_guaranteed\":true,
\"$canonical_identifier\":\"room/OrangeOak\",
\"$exp_date\":0,
\"$identity_id\":\"308073965526600507\",
\"$og_title\":\"Orange Oak\",
\"$one_time_use\":false,
\"$publicly_indexable\":1,
\"room_name\":\"Orange Oak\", // this is a custom param, of which you may have an unlimited number
\"~channel\":\"pasteboard\",
\"~creation_source\":3,
\"~feature\":\"sharing\",
\"~id\":\"319180030632948530\",
\"+click_timestamp\":1477336707,
\"~referring_link\":\"https://branchmaps.app.link/qTLPNAJ0Jx\"
}";
"device_fingerprint_id" = 308073965409112574;
"identity_id" = 308073965526600507;
link = "https://branchmaps.app.link/?%24identity_id=308073965526600507";
"session_id" = 319180164046538734;
}
You can read more about these parameters on the Branch documentation here.
Hmm... as far as I'm aware, there's not really anything you can catch in the application:(_:didFinishLaunchingWithOptions) phase that would let you know the app was being opened by a dynamic link. You're going to have to wait until the continueUserActivity call, as you mentioned.
That said, FIRDynamicLinks.dynamicLinks()?.handleUniversalLink returns a boolean value nearly instantly, so you should be able to take advantage of that to short-circuit your into animation without it being a bad user experience. The callback itself might not happen until several milliseconds later, depending on if it's a shortened dynamic link (which requires a network call) or an expanded one (which doesn't).
I added a url scheme newConversation to my info.plist so that when a user clicks on a link in a browser/email it will redirect him to the app. This works perfectly fine.
I was wondering how I can open the app to a specific view controller when clicking this url?
I tried using:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
if ([url.scheme isEqualToString:#"newConversation"]) {
NSLog(#"url schemeeeee");
emailLink = YES;
[self initWindow];
}
return YES;
}
but not getting anywhere with that.
Thanks!
Your app should only have one scheme (such as myappscheme) registered for your app. Then if you need to handle different actions, you provide more specific URLs with that scheme:
myappscheme:///newConversation
myappscheme:///doSomethingElse
Then you get /newConversation for the URL's path. Then your code becomes:
if ([url.path isEqualToString:#"/newConversation"]) {
} else if ([url.path isEqualToString:#"/doSomethingElse"]) {
}
To be honest, this is kind of a pain to implement on your own. A basic custom URL scheme link isn't an ideal solution and has a ton of nasty edge cases, most notably the 'Cannot Open Page" error users will see before if they don't have your app installed, and the fact that many apps actually don't recognize custom scheme URLs as 'clickable' (they just show up as regular text).
A somewhat better approach is to use a regular http:// link, and then redirect the visitor to your app — if they have it installed — or to a fallback URL/App Store page. Until iOS 9, a reasonable basic implementation was a JavaScript redirect like this:
setTimeout(function() {
window.location = "https://itunes.apple.com/path/to/your/app/";
}, 25);
// If "yourapp://" is registered, the user will see a dialog
// asking if want to open your app. If they agree, your app will
// launch immediately and the timer won't fire.
// If not installed, you'll get an ugly "Cannot Open Page"
// dialogue and the App Store will launch when the timer expires.
window.location = "yourapp://";
Unfortunately this would still show a 'Cannot Open Page' error, but until recently it was possible to get around this in a reasonably user-friendly way by using a more nuanced version of this script. Sadly, Apple intentionally broke that with the iOS 9.2 update, so custom URL schemes are actually pretty much useless for deep linking now, unless you are certain the app is already installed on that device.
The best solution is a combination of custom URL scheme links (with intelligent JavaScript redirections) and Apple's new Universal Links. Universal Links let you use a normal http:// URL to a page on your website (the page could be a simple redirection to the App Store without the custom URL trigger that causes the 'Cannot Open Page' error), which is intercepted by your phone and sent directly into your app if installed. Unfortunately Universal Links only work in iOS 9+, and don't work yet when opened inside a lot of apps.
This is quite a lot to handle, so the best option might be a free service like Branch.io (full disclosure: I work with the team) to take care of all the technical aspects.
This SO post addresses how to customize the UIActivityViewController by excluding services like AirDrop or printing.
It also mentions this Apple doc which highlights the stock services supported, but how do we identify other supported end points like Line and other messaging apps?
Specifically:
(1) Do Skype, Kakao, Line, Viber, WeChat, Kik, WhatsApp, and Facebook Messenger (not Facebook proper) have end points?
(2) What are those end points?
You can't do that currently on iOS 7, because no application can talk directly to other applications yet for security reasons. One of the highlights of the last WWDC was the introduction of extensions for iOS 8, which will make this possible; you can read how in the Creating Action Extensions example.
There are however attempts at fixing this. A notable example is IntentKit, which works by having a repository of known apps.
What is IntentKit?
IntentKit is an open-source iOS library that makes it easier to link to other apps. It's sort of like Android's Intents or Windows Phone's Contracts.
Another example of one of such attempts is OvershareKit
Why OvershareKit?
Sharing is far too cumbersome to implement on iOS. UIActivityViewController is too limiting, and rolling your own library is too time-consuming. Most devs end up settling for underwhelming sharing options for lack of the time or inclination to make something better.
OvershareKit makes it trivial to add rich sharing options to your iOS apps.
How to know if an application is installed?
Even though you can't discover them. If you know the application you're looking for and what kind of URL Scheme it responds to, then you can check if your app is able to open that kind of URL.
That's what IntentKit is for, it's a repository of knowledge about applications, the URL Schemes they respond to and the kind of actions they can perform. With the introduction of extensions.
For example, you can check if Facebook is installed by checking if you can open a fb:// URL.
BOOL isFacebookInstalled = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"fb://"]];
About IntentKit's inner workings
Internally, IntentKit will check for that same thing, as you can see in INKActivity's implementation:
- (BOOL)canPerformCommand:(NSString *)command {
if (!self.actions[command]) { return NO; }
if (self.presenter) {
return [self.presenter canPerformAction:command];
} else {
NSURL *url = [NSURL URLWithString:[self.actions[command] urlScheme]];
return [self.application canOpenURL:url];
}
}
Info about requested UIActivity services:
Skype uses the "skype:" URI, more info in the official documentation
Kakao & Line, with DCActivity (there seems to be an official API for Kakao, but the documentation is in korean)
Line, with LINEActivity
WeChat, with WeixinActivity (there's also an official API with which you can make your own UIActivity)
WhatsApp uses the "whatsapp:" URI, more info on the official FAQ, there are also various UIActivity implementations for WhatsApp, take a look at them in cocoapods.com
Facebook Messenger uses the "fb-messenger:" URI, more info in this other answer by tia, also see workarounds.
Kik has a public API, but no SDK nor UIActivity implementation that I know of. Also, see workarounds.
Viber has no SDK nor public API, see workarounds.
Workarounds
Most of these services are based on known protocols, or slight variations of them. For example, you can use XMPP (aka Jabber) to directly send messages to a Facebook IM or Kik account; some people say that Viber seems to use a modification of SIP for signaling with VoIP phones. So you could work around some SDK/API limitations by using the underlying mechanisms.
SDK or API?
If all you need is to send a message to those services, I'd argue that you don't really need to communicate with the installed application via an SDK or URL Schemes, I haven't been able to test the Big Emoji app you mentioned, as it just crashes on iOS 8, but if it's using the services API's, you could easily work it out by using Charles or Wireshark.
Presumably they are adding a bunch of their own custom actions, as described in this answer.
There is no central repository for third-party sharing support before iOS 8. You can check for the other apps' presence by using URL Schemes. To do this, you'll have to look at each app's documentation and figure out what schemes they accept, then do something like this:
NSArray* items = /* stuff you want to share */
NSMutableArray* activities = NSMutableArray.array;
if ([UIApplication.sharedApplication canOpenUrl:#"whatsapp://url"])
{
UIActivity* activity = /* create activity for whatsapp */
[activities addObject:activity];
}
if ([UIApplication.sharedApplication canOpenUrl:#"facebook://url"])
{
UIActivity* activity = /* create activity for facebook */
[activities addObject:activity];
}
// ... repeat for other services ...
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:activities];
// show the VC however appropriate.
In addition to #NinoScript, you can find here the URL schemes for the iOS apps (inside the .plist files) which is provided by IntentKit as he mentioned.
Here is a summarized list from the project:
1Password ophttps://{{{url}}}
Chrome: googlechromes://{{{url}}}
Gmail: googlegmail:///co?to={{recipient}}
Google Maps: comgooglemaps://?q={{query}}
Google+: gplus://plus.google.com/{{userId}}
Safari: http://{{{url}}}
For a full URL-schemes search the git project.
I'm looking at integrating support for tracking Facebook's new mobile app ads.
I've read the tutorial here:
https://developers.facebook.com/docs/tutorials/mobile-app-ads/
It says:
Include the following code to be executed when your app opens for the first time by user
[FBSettings publishInstall:appId];
So the first question is - where do I put this so that it only invokes the call if the install was driven from Facebook? I don't want FB to get credit for someone who found my app themselves on the app store.
Do I need to manually track whether or not I've called the publishInstall before for this specific user? (The preamble sentence implies this - but the SDK documentation for publishInstall implies otherwise).
And even more confusing is that the SDK FBSettings reference includes shouldAutoPublishInstall which defaults to YES. This would suggest that I don't need to do anything other than have the SDK integrated. So why does the tutorial not mention this as an option?
I assume that the appId is the associated Facebook appId (as opposed to the App Store App ID). This is also not clear from the documentation.
I browsed the sources of facebook iOS SDK, and it seems that guide is wrong.
You are right, that autoPublishInstall is set to YES by default, which means we don't need to invoke [FBSettings publishInstall:appId]; manually. AppId is indeed the facebook app id.
When you invoke openActiveSessionWith.... method, it initializes FBSession with
initWithAppID:permissions:defaultAudience:urlSchemeSuffix:tokenCacheStrategy: which contains in the end [FBSettings autoPublishInstall:self.appID];
+ (void)autoPublishInstall:(NSString *)appID {
if ([FBSettings shouldAutoPublishInstall]) {
dispatch_once(&g_publishInstallOnceToken, ^{
// dispatch_once is great, but not re-entrant. Inside publishInstall we use FBRequest, which will
// cause this function to get invoked a second time. By scheduling the work, we can sidestep the problem.
[[FBSettings class] performSelector:#selector(publishInstall:) withObject:appID afterDelay:FBPublishDelay];
});
}
}
So technically it should report the install out of the box (if I'm not missing something). I'm going to play with it a little more today to see if it works as expected and update answer with results.
Just put it at -[application:didFinishLaunchingWithOptions].
Not all of the apps want to integrate the Facebook login. They only want the feature "mobile app install ads". For these kind of app, they should invoke +[FBSettings publishInstall:appId] manually. On the other hand, if your app has already integrated facebook login, you can assume that the FB sdk has published the installation.
If we just have to put
[FBSettings publishInstall:appId];
manually in
-[application:didFinishLaunchingWithOptions]
how will I identify which install happened from facebook? I don't want FB to get credit for someone who found my app themselves on the app store.
put the code in Appdelegate DidbecomeActive method
- (void)applicationDidBecomeActive:(UIApplication *)application
hope this help :)
Right now I'm able to launch something like the mail app with this call:
NSURL* mailURL = [NSURL URLWithString: #"emailAddress#example.com?cc=&subject=Feedback"];
[[UIApplication sharedApplication] openURL: mailURL];
However, what I'd like is for mail to return control back to my app automatically once the user finishes sending an email. Is this possible?
This is not possible at the moment.
There has been a bit of an effort to try and get apps to accept a url callback parameter that would indicate which app control should be returned to.
See http://x-callback-url.com/
The goal of the x-callback-url specification is to provide a standardized means for iOS developers to expose and document the methods they make available to other apps. Using x-callback-url’s source apps can launch other apps passing data and context information, and also provide parameters instructing the target app to return data and control back to the source app after executing an action. Specific supported actions will be dependent on the individual apps and will not be discussed in the specification.
but obviously without an 'official' solution you'd never get control back from mail.app.
I've written up the various strategies for integrating iOS applications in some detail today. It sounds like MFMailComposeView is all you need, but in case anyone else comes to read this question on more general integration grounds:
http://blog.codiform.com/2011/04/integrating-applications-in-ios-and-x.html
X-callback-url is definitely the promising (but relatively new) entrant to the return-to-caller model you'd otherwise need.