Skip to content
This repository has been archived by the owner on Apr 2, 2018. It is now read-only.

App Rejected due to UIWebBrowserView Non Public API Usage #179

Closed
sam-coley opened this issue Mar 17, 2016 · 47 comments · Fixed by #197
Closed

App Rejected due to UIWebBrowserView Non Public API Usage #179

sam-coley opened this issue Mar 17, 2016 · 47 comments · Fixed by #197

Comments

@sam-coley
Copy link

The latest version of our app has just been rejected by apple for the use of the Non Public API UIWebBrowserView which is used in this plugin.

We've been using this plugin for a while with no problems but it seems Apple are now onto the use of UIWebBrowserView.

Full message from Apple:

Your app uses or references the following non-public APIs, which is a violation of the App Store Review Guidelines:

UIWebBrowserView

The use of non-public APIs is not permitted in the App Store because it can lead to a poor user experience should these APIs change.

Next Steps

Please revise your app to remove any non-public APIs. If you have defined methods in your source code with the same names as the above-mentioned APIs, we suggest altering your method names so that they no longer collide with Apple's private APIs to avoid your application being flagged in future submissions.

Additionally, if you are using third party libraries, please update to the most recent version of those libraries. If you do not have access to the libraries' source, you may be able to search the compiled binary using the "strings" or "otool" command line tools. The "strings" tool can output a list of the methods that the library calls and "otool -ov" will output the Objective-C class structures and their defined methods. These tools can help you narrow down where the problematic code resides. You could also use the "nm" tool to verify if any third party libraries are calling these APIs.

@TheRocktopus
Copy link

+1

Just had an app rejected by Apple a half hour ago for the same reason. This plugin is the only place I can find UIWebBrowserView in my source.

Edit March 22: App has been approved with the latest update. Thanks!! :)

@mubasshir
Copy link

+1
I just came here to post but you posted already!

I investigated found out to be hackishlyFoundBrowserView is non-public API which is we are using.

@DimDrop
Copy link

DimDrop commented Mar 18, 2016

Also had same issue today with app rejection due to using uiwebbrowserview non-public api.

@vijaydarkonde
Copy link

So Mubasshir, Have you found solution to fix it

@mubasshir
Copy link

For now I've removed plugin and submitted to store. Waiting for team to resolve. I suggest you guys to use older version if possible

@vijaydarkonde
Copy link

I have also remove the plugin and submitted back. App is now using default keyboard which is fine for my app.

@DimDrop
Copy link

DimDrop commented Mar 18, 2016

Removed plugin and submitted for review. Using default keyboard without issue,

@diegodotta
Copy link

+1 Sad!

@vijaydarkonde
Copy link

My App is in review, I will keep posted.

@tony--
Copy link
Contributor

tony-- commented Mar 18, 2016

This SO post has a different implementation that looks to do the same thing as UIWebViewExtension's hackishlyHidesInputAccessoryView, but it does not contain the string UIWebBrowserView.
I didn't test it, but it has a 15 upvotes and positive feedback in the comments.
Worth a look?

@mhartington
Copy link
Member

Thanks for reporting this issues everyone. We'll take care of this ASAP!

@ghaiat
Copy link

ghaiat commented Mar 18, 2016

+1

2 similar comments
@rwvandenberg
Copy link

+1

@ravitejk
Copy link

+1

@tlancina
Copy link
Contributor

Hey everyone, I've just published version 2.0.0 of the plugin, which removes the hideKeyboardAccessoryBar method for the moment. I also published it as 1.0.9, so it's within a valid semver range for people using config.xml to save their plugins. I think your app being rejected by the App Store takes precedence over semver semantics on this one.

@tony-- thanks for the link! Looks like if([[view.class description] hasPrefix:@"UIWeb"]) might get around Apple's static analysis (if that's what they're doing).

We'll add it back in once we can make sure that apps aren't getting rejected. There's no public API for removing the accessory bar as far as I know, so we'll also be more clear that this is always a danger.

To update, do

cordova plugin rm ionic-plugin-keyboard
cordova plugin add ionic-plugin keyboard

Thanks for your patience everyone, and sorry for the headache! ❤️

@xereda
Copy link

xereda commented Mar 18, 2016

+1

@vijaydarkonde
Copy link

Apple approved my App after I removed this plugin!

@cjwirth
Copy link

cjwirth commented Mar 19, 2016

I've released apps with this same technique -- as hacky as it may be -- so it's quite disappointing that they would start rejecting apps for this (although I realize it is using a private API, so they are well within their rights to do so).

I'm not sure if using @"UIWeb" instead of @"UIWebBrowserView" is such a good idea, because you're still doing the same thing. Yes, it will probably get past a static analyzer, but you still end up using the same private API.

Still, I don't have any other solutions for it. I've submit a bug report requesting the ability to hide or replace the inputAccessoryView for web views.

@kiwox
Copy link

kiwox commented Mar 22, 2016

+1
I have been rejected last week.

@mhartington
Copy link
Member

@kiwox sorry about that. If you remove the plugin and then add it back, you'll be good to go

@markshust
Copy link

I submitted a PR for this issue last week. hideKeyboardAccessoryBar works, app store approved my submission 2 days ago. Just merge it in, baby.

#177

@EddyVerbruggen
Copy link

@markoshust We can hack around the rejection cause by using substrings of the offending string, or just grabbing the first child of the scrollview, etc, but at the end of the day at runtime you're still using a private API risking your app to crash in case Apple decides to change the implementation of the private class used.

In all honesty I don't expect Apple to change anything until iOS 20, but there may be a good reason for them now actively checking for usage of this private class. It's such a PITA that that a) that entire process is so intransparent and b) they don't just play nice and make it easy for webdevs to do cool things with the keyboard. I'm guessing it has something to do with trying to delay the inevitable future success of progressive web apps (vs the status quo where Apple controls everything through their AppStore).

@markshust
Copy link

@EddyVerbruggen it's aggravating & frustrating, I completely agree with all your points. They are moving the wrong direction with this, if anything they should be opening more private APIs and making them public.

I don't see things changing in the near future, so I'll be using this until another method is implemented. There has to be a clean way to implement this... no?

@markshust
Copy link

Thinking this through, there should be a way to gracefully fallback/fail in the event the private API is not found, to avoid crashes.

Method UIMethod = class_getInstanceMethod(NSClassFromString(@"UIWebBrowserView"), @selector(inputAccessoryView));

Is there a way to check for the existence of the private class, ex. https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/#//apple_ref/occ/clm/NSObject/instancesRespondToSelector: -- and just exit the script if not found? This would allow us to implement private methods, and not have our app crash if they are not found. IMO this is a decent workaround.

This landscape changes quick, and some of us really need this functionality inside our apps (and graceful fail is OK because we can re-implement a solution and re-submit in that event).

@tlancina
Copy link
Contributor

@markoshust thanks for the PR! We're investigating our options at this point, because as @cjwirth and @EddyVerbruggen mentioned there is still a risk at any point when using private APIs. My concern is we may have used our one "free pass" and I would like to avoid causing apps to be rejected en masse in the future again.

I understand people want this functionality though, so perhaps we pull it out into its own plugin with the explicit warning that you are risking rejection?

@tlancina
Copy link
Contributor

Or if it's working, do we pass the torch to https://github.com/cjpearson/cordova-plugin-keyboard? We originally created this plugin because the Cordova one was unmaintained and now the reverse has sort of happened.

@EddyVerbruggen
Copy link

@tlancina Except for the fact that they have an explicit hack in their code which ends up calling into the same runtime private API: https://github.com/cjpearson/cordova-plugin-keyboard/blob/master/src/ios/CDVKeyboard.m#L124-L125

I really feel bad for all users of this plugin. I'm exploring possibilities as well as I kinda need this feature badly myself.

@markshust
Copy link

Instead of researching for possible 'correct' fixes for the next ~3-6 months, we can implement graceful fallback... today? Shouldn't be difficult. I know it's not 'correct', but it works, and it works today. After that's done, that will buy some time to implement 'correct' fixes.

@keenan35i
Copy link

A temporary fix for the keyboard accessory bar would be great, especially because its been working for cordova keyboard plugin for all this time, maybe even create a mirrored plugin with this implementation and a warning would be ok too. if this was up today that would be awesome!

@markshust
Copy link

@keenan35i i have this repo with the PR submitted, you can use if you wish. this is the one i'm using in prod. here's the url:

ionic-plugin-keyboard@https://github.com/markoshust/ionic-plugin-keyboard.git#9b0ff0c0f5cfc5672941ffaa9664b35bb2920bee

@keenan35i
Copy link

Thanks

@tony--
Copy link
Contributor

tony-- commented Mar 23, 2016

@tlancina , @EddyVerbruggen
It seems like the important part of all of these techniques is not to access a Private API.
Instead, the important part is to swizzle in a replacement for inputAccessoryView which is not a private API.

The part that runs afoul of the "private API" rule is detecting the undocumented class "UIWebBrowserView". Is that really needed? Would it be possible to instead detect if any of the views in the hierarchy have inputAccessoryView: and just swizzle it in without comparing the class to strings that may run afoul of the "private API" check? Or even just swizzle in inputAccessoryView on all of the scrollView subviews, regardless of whether they are any particular class?

In other words, use this technique but skip the classname check?

Edit: fixed link.

@markshust
Copy link

This seems close to what I was thinking (with early exits), but a bit more
elegant. Are there any performance implications of this, since we would be
iterating on every view?
On Wed, Mar 23, 2016 at 10:56 AM Tony Homer notifications@github.com
wrote:

@tlancina https://github.com/tlancina , @EddyVerbruggen
https://github.com/EddyVerbruggen
It seems like the important part of all of these techniques is not to
access a Private API.
Instead, the important part is to swizzle in a replacement for
inputAccessoryView which is not a private API
https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/InputViews/InputViews.html
.

The part that runs afoul of the "private API" rule is detecting the
undocumented class "UIWebBrowserView". Is that really needed? Would it be
possible to instead detect if any of the views in the hierarchy have
inputAccessoryView: and just swizzle it in without comparing the class to
strings that may run afoul of the "private API" check? Or even just swizzle
in inputAccessoryView on all of the scrollView subviews, regardless of
whether they are any particular class?

In other words, use [this technique|
http://stackoverflow.com/a/19042279/273628] but skip the classname check?


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#179 (comment)

@NoahYarian
Copy link

Our app was approved yesterday using the new version. Thanks for being so responsive!

@cjwirth
Copy link

cjwirth commented Mar 25, 2016

As a "temporary" fix, it would probably work to release something like they are doing elsewhere -- looping through the views and swizzling the inputAccessoryView; using an array of ["UI", "Web", "Browser", "View"] to rebuild the class's name; etc... Given the nature of the problem, I'm not sure we have much of a choice but to swizzle the method of the private class one way or another 😕

The problem I have with it is that, well, we all know what happens with "temporary" fixes, don't we?

@tlancina
Copy link
Contributor

@tony-- interesting thought, I'm not familiar with the technicalities of Apple's TOS, does finding a private class through public APIs count as breaking the rules?

EDIT: thinking about it, I think yes obviously any use of a private class would technically considered bad :)

@lbickston
Copy link

any updates?

@jacquesdev
Copy link

Any updates on this, would be super useful to get it back?

@markshust
Copy link

I'll quote what I said nearly a month ago:

Instead of researching for possible 'correct' fixes for the next ~3-6 months, we can implement graceful fallback... today? Shouldn't be difficult. I know it's not 'correct', but it works, and it works today. After that's done, that will buy some time to implement 'correct' fixes.

@diegodotta
Copy link

diegodotta commented Apr 21, 2016

What's the problem with "inputAccessoryView"? This is great feature for the user, although the html html object SELECT needs this bar to confirm the selection.

@tlancina
Copy link
Contributor

tlancina commented Apr 25, 2016

Hey all, I think the best risk-reward balance here is to do what @tony-- suggested in #179 (comment).

PRs welcome as I currently won't be able to look into this until next week.

@luco
Copy link

luco commented Apr 26, 2016

Really looking forward for this fix. In some cases, this is a must.

@tony--
Copy link
Contributor

tony-- commented Apr 26, 2016

@tlancina Please take a look at #197 and see what you think

@tlancina
Copy link
Contributor

tlancina commented May 10, 2016

hideKeyboardAccessoryBar() has been added back in as of 2.2.0 thanks to @tony--. If we see apps starting to get rejected again we will reach out to Apple to see about exposing inputAccessoryView on UI/WKWebView publicly.

Thanks everyone for your patience!

@mstueve
Copy link

mstueve commented May 12, 2016

Thanks, @tony-- and @tlancina! Is there any way to hide the predictive QuickType suggestion bar in iOS? I know the user can manually do it but wasn't sure if there's a way to via the API as well.

@markshust
Copy link

@mstueve in react, add this to input type:

                autoComplete='off'
                autoCorrect='off'
                autoCapitalize='off'
                spellCheck='off'

@mstueve
Copy link

mstueve commented May 12, 2016

@markoshust thanks! That works great.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.