After ios 11 upgrade WKWebView does not load my website.
I added the following methods to my renderer:
[Export("webViewWebContentProcessDidTerminate:")]
public virtual void ContentProcessDidTerminate(WKWebView webView)
{
Console.WriteLine("ContentProcessDidTerminate");
}
[Export("webView:didFinishNavigation:")]
public void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false;
Console.WriteLine("DidFinishNavigation");
}
[Export("webView:didFailNavigation:withError:")]
public void DidFailNavigation(WKWebView webView, WKNavigation navigation, NSError error)
{
// If navigation fails, this gets called
Console.WriteLine("DidFailNavigation:" + error.ToString());
}
[Export("webView:didFailProvisionalNavigation:withError:")]
public void DidFailProvisionalNavigation(WKWebView webView, WKNavigation navigation, NSError error)
{
// If navigation fails, this gets called
Console.WriteLine("DidFailProvisionalNavigation" + error.ToString());
}
[Export("webView:didStartProvisionalNavigation:")]
public void DidStartProvisionalNavigation(WKWebView webView, WKNavigation navigation)
{
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;
// When navigation starts, this gets called
Console.WriteLine("DidStartProvisionalNavigation");
}
The application shows didStartProvisionalNavigation but it does not move to the other methods.
I also added the following to my info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
I also tried running my WKWebview against another of my sites and that one works just fine.
In Safari, after attaching it to my WKWebView, I can see that some webapi calls does not seem to complete the request. Yet in some cases the same request does complete but some others does not.
Is anyone having this type of issue?
Any help to solve this would be really appreciated.
Thanks,
Hector
UPDATE:I found the solution
It turns out that in iOS 11 the WKWebView is trying to navigate to "about:blank". I'm not aware of our site having that "page".
Then in my DecidePolicy that page was not allowed and I guess that was also stopping my website from continuing loading normally. I added a condition to enable navigation to "about:blank" and that solved it.
[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action<WKNavigationActionPolicy> decisionHandler)
{
var navType = navigationAction.NavigationType;
var targetFrame = navigationAction.TargetFrame;
var url = navigationAction.Request.Url;
if (
url.ToString().StartsWith("http") && (targetFrame != null && targetFrame.MainFrame == true)
)
{
decisionHandler(WKNavigationActionPolicy.Allow);
}
else if (
(url.ToString().StartsWith("http") && targetFrame == null)
||
url.ToString().StartsWith("mailto:")
|| url.ToString().StartsWith("tel:")) //Whatever your test happens to be
{
//decisionHandler(WKNavigationActionPolicy.Allow);
Device.BeginInvokeOnMainThread(() =>
{
UIApplication.SharedApplication.OpenUrl(url);
}
);
} // NEED THIS IN IOS 11
else if ( url.ToString().StartsWith("about") )
{
decisionHandler(WKNavigationActionPolicy.Allow);
}
}
Hope this helps someone else. :)
Related
my web application offers a download. Javascript creats at the click the url (it depends on the user input) and the browser should open it, so that the page isn't reloaded.
For that, I think I have to alternatives:
// Alt1:
window.open(pathToFile);
// Alt2:
var downloadFrame = document.getElementById('downloads');
if (downloadFrame === null) {
downloadFrame = document.createElement('iframe');
downloadFrame.id = 'downloads';
downloadFrame.style.display = 'none';
document.body.appendChild(downloadFrame);
}
downloadFrame.src = pathToFile;
Both works under Firefox. Problem with open new window method: If the creation of the file at the server needs more time, the new empty tab will be closed late.
Problem with iframe: If there is an error at the server, no feedback is given.
I think at firefox the iframe is the better solution. But the web application must run with an JavaFX WebView, too. JavaFX haven't a download feature, I have to write it. One possible way is to listen on the location property:
final WebView webView = new WebView();
webView.getEngine().locationProperty().addListener(new ChangeListener<String>() {
#Override public void changed(ObservableValue<? extends String> observableValue, String oldLoc, String newLoc) {
if (newLoc.cotains("/download")) {
FileChooser chooser = new FileChooser();
chooser.setTitle("Save " + newLoc);
File saveFile = chooser.showSaveDialog(webView.getEngine().getScene().getWindow());
if (saveFile != null) {
BufferedInputStream is = null;
BufferedOutputStream os = null;
try {
is = new BufferedInputStream(new URL(newLoc).openStream());
os = new BufferedOutputStream(new FileOutputStream(saveFile));
while ((readBytes = is.read()) != -1) {
os.write(b);
}
} finally {
try { if (is != null) is.close(); } catch (IOException e) {}
try { if (os != null) os.close(); } catch (IOException e) {}
}
}
}
}
}
There are some problems:
The download start depends on a part of the url, because JafaFX supports no access to the http headers (that is bearable)
If the user starts the download with the same url two times, only the first download works (the change event only fires, if the url is new). I can crate unique urls (with #1, #2 and so on at the end). But this is ugly.
Only the "window.open(pathToFile);" method works. Loading an iframe don't fire the change location event of the website. That is expectable but I haven't found the right Listener.
Can someone help me to solve 2. or 3.?
Thank you!
PS: Sorry for my bad english.
edit:
For 2. I found a way. I don't know if it is a good one, if it is performant, if the new webview is deleted or is in the cache after download, ....
And the user don't get an feedback, when some a problem is raised:
webView.getEngine().setCreatePopupHandler(new Callback<PopupFeatures, WebEngine>() {
#Override public WebEngine call(PopupFeatures config) {
final WebView downloader = new WebView();
downloader.getEngine().locationProperty().addListener(/* The Listener from above */);
return downloader.getEngine();
}
}
I think you may just need to use copyURLtoFile to get the file...call that when the location changes or just call that using a registered java class. Something like this:
org.apache.commons.io.FileUtils.copyURLToFile(new URL(newLoc), new File(System.getProperty("user.home")+filename));
Using copyURLToFile the current page doesn't have to serve the file. I think registering the class is probably the easiest way to go... something like this:
PHP Code:
Download $filename
Java (in-line class in your javafx class/window... in this case my javafx window is inside of a jframe):
public class JavaApp {
JFrame cloudFrameREF;
JavaApp(JFrame cloudFrameREF)
{
this.cloudFrameREF = cloudFrameREF;
}
public void getfile(String filename) {
String newLoc = "http://your_web_site.com/send_file.php?filename=" + filename;
org.apache.commons.io.FileUtils.copyURLToFile(new URL(newLoc), new File(System.getProperty("user.home")+filename));
}
}
This part would go in the main javafx class:
Platform.runLater(new Runnable() {
#Override
public void run() {
browser2 = new WebView();
webEngine = browser2.getEngine();
appREF = new JavaApp(cloudFrame);
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<State>() {
#Override public void changed(ObservableValue ov, State oldState, State newState) {
if (newState == Worker.State.SUCCEEDED) {
JSObject win
= (JSObject) webEngine.executeScript("window");
// this next line registers the JavaApp class with the page... you can then call it from javascript using "app.method_name".
win.setMember("app", appREF);
}
}
});
You may not need the frame reference... I was hacking some of my own code to test this out and the ref was useful for other things...
I'm pretty new to Xamarin and iOS and I am putting together a small POC app to learn.
The app uses a view with a UIWebView in it. I intend to load local web files into it and then manipulate those with a mix of JavaScript and C# (JS bridge) but the UIWebView doesn't function properly. It simply doesn't load the pages. If I uninstall the app and then re-install and run it it often load the web page successfully but in subsequent sessions the web page usually doesn't load, leaving the web view as a white empty area. Just to be sure I have tried to load well known web pages, such as xamarin.com or apple.com. Again, the first time I run it the pages load but in subsequent sessions they don't. The behavior is the same on a physical device (an iPhone 5) and on the simulator.
Are there considerations as to how the UIWebView needs to be used? The ViewController hosting it is a sub view of a SlideoutNavigationController.
I have very little experience with UIWebView so any hints are greatly appreciated.
Thanks
EDIT:
The code that loads the web view:
WebView.LoadFinished += (object sender, EventArgs e) =>
{
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false; System.Diagnostics.Debug.WriteLine("Web page was loaded");
};
WebView.LoadError += (object sender, UIWebErrorArgs e) =>
{
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false; System.Diagnostics.Debug.WriteLine(e);
};
WebView.ShouldStartLoad += (UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType) =>
{
return true;
};
WebView.Layer.BorderColor = UIColor.Red.CGColor;
WebView.Layer.BorderWidth = 10;
WebView.LoadRequest(new NSUrlRequest(new NSUrl("http://www.apple.com")));
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;
WebView.ScalesPageToFit = true;
Note: The ShouldStartLoad event handler is the only one that gets invoked.
This is what the iOS simulator looks like after page (failed to) load:
Figured it out. It turned out loading WebView with (extremely light) HTML content, such as a HTML string, actually required more time than fetching my own objects from a web service. When the data returned by the web service became available WebView still hadn't completed its loading of the web page. So, as my client code got the web service result there was no web page to decorate and the code got in the way of WebViews normal load operation.
This code fixed the sync problem:
private ManualResetEvent _syncLoadWebPage = new ManualResetEvent(false);
private void loadEmptyWebPage()
{
const string EmptyHtml =
"<html><head></head><body style=\"background: aquamarine\"><H1>EMPTY PAGE</H1></body></html>";
WebView.LoadFinished += (sender, e) => webPageReady();
WebView.LoadStarted += (sender, e) => UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;
WebView.LoadHtmlString(EmptyHtml, new NSUrl(AppDelegate.Current.Data.Web.LocalWebRoot, true));
WebView.ScalesPageToFit = true;
}
private void webPageReady()
{
_syncLoadWebPage.Set();
}
void whenWebPageIsReady(Action callback)
{
Task.Factory.StartNew(() =>
{
_syncLoadWebPage.WaitOne();
callback();
});
}
void bindMapFile()
{
whenWebPageIsReady(() =>
{
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false;
var snap = new Snap(new WebViewImpl(WebView));
snap.Image(Model.MapFilePath, "map", 0, 0, Model.MapSize.Width, Model.MapSize.Height);
// todo Decorate stuff with map here ...
});
}
All I had to do was to wait for the web page to finish loading and everything seems to work. I simply drew the wrong conclusions from what I saw.
Silly, really.
I've written a custom MvxTouchViewPresenter that allows me to show either a SlidingPanel (RootView), or show a MvxTabBarViewController (AuthView).
When my app launches,
if I tell it to load the TabBarView (AuthView), it works as expected.
if I tell it to load the SlidingPanelView (RootView), it also works as expected.
The problem occurs when I load the AuthView and then try to ShowViewModel<RootView>()... basically what happens in this scenario is that I stay at the AuthView, even though I see the CustomPresenter.Show() method has run appropriately.
Here's the method
public override void Show(MvxViewModelRequest request)
{
var viewController = (UIViewController)Mvx.Resolve<IMvxTouchViewCreator>().CreateView(request);
RootController = new UIViewController();
// This needs to be a Tab View
if (request.ViewModelType == typeof(AuthViewModel))
{
_navigationController = new EmptyNavController(viewController);
RootController.AddChildViewController(_navigationController);
RootController.View.AddSubview(_navigationController.View);
}
if (request.ViewModelType == typeof(RootViewModel))
{
_navigationController = new SlidingPanelsNavController(viewController);
RootController.AddChildViewController(_navigationController);
RootController.View.AddSubview(_navigationController.View);
AddSlidingPanel<NavigationFragment>(PanelType.LeftPanel, 280);
}
base.Show(request);
}
And here's a Gist of the complete class
What am I missing in trying to make this work appropriately?
not sure if what I've done is "correct" but it's working for now. I'm still very much open to better answers.
What I've done in order to simply move on from this problem is add a call to SetWindowRootViewController(_navigationController); just before I call base.Show(request)
public override void Show(MvxViewModelRequest request)
{
_navigationController = null;
var viewController = (UIViewController)Mvx.Resolve<IMvxTouchViewCreator>().CreateView(request);
RootController = new UIViewController();
// This needs to be a Tab View
if (request.ViewModelType == typeof(AuthViewModel))
{
_navigationController = new EmptyNavController(viewController);
RootController.AddChildViewController(_navigationController);
RootController.View.AddSubview(_navigationController.View);
}
else if (request.ViewModelType == typeof (RootViewModel))
{
_navigationController = new SlidingPanelsNavController(viewController);
RootController.AddChildViewController(_navigationController);
RootController.View.AddSubview(_navigationController.View);
AddSlidingPanel<NavigationFragment>(PanelType.LeftPanel, 280);
}
else
{
throw new Exception("They View Type you're trying to show isn't currently supported.");
}
// RIGHT HERE
SetWindowRootViewController(_navigationController);
base.Show(request);
}
I am trying to display a webpage from codenameone application. It works fine in Iphone but not in andriod mobile.
public void showLoginForm()
{
final Form loginForm = new Form("Login");
loginForm.setUIID("Form1");
loginForm.setLayout(new BorderLayout());
loginForm.setScrollable(false);
try
{
WebBrowser browser = new WebBrowser()
{
//Overrides onStart and onLoad methods to load progress bars for page transitions.
};
browser.setURL(appsGlobalSettings.get(URL_KEY));
loginForm.addComponent(BorderLayout.CENTER,browser);
loginForm.show();
}
catch(Exception e)
{
e.printStackTrace();
}
}
Check your URL. I suggest removing the override code and placing Google.com hardcoded as a URL and proceeding from there. Since it doesn't work in the simulator either make sure you are using Java 7 with JavaFX enabled, with that case it should show a browser and should work for a proper web address.
Shai...It took some time to collect device logs.
But even that is intriguing.
I tried in two ways
No overridden methods in WebBrowser class and http://www.google.co.in as URL.
public void showLoginForm()
{
InfiniteProgress inf = new InfiniteProgress();
Dialog progress = inf.showInifiniteBlocking();
final Form loginForm = new Form("Login");
loginForm.setUIID("Form1");
loginForm.setLayout(new BorderLayout());
try
{
//Log.p("Inside showLoginForm method");
WebBrowser browser = new WebBrowser()
{
};
//browser.setURL(appsGlobalSettings.get(URL_KEY));
browser.setURL("http://www.google.co.in");
//Log.p("Set Broswer url");
loginForm.addComponent(BorderLayout.CENTER,browser);
//Log.sendLog();
loginForm.show();
}
catch(Exception e)
{
e.printStackTrace();
//Log.p(e.toString());
}
}
Result - the same blank screen.
Same code with added Log.p and Log.send() statements.
Result - it works fine
Is this due to some race condition?
I'm trying to implement Game Center into my game but i have problems with it.
Here is my Main.cs code :
namespace iosgame
{
public class Application
{
[Register ("AppDelegate")]
public partial class AppDelegate : IOSApplication {
MainViewController mainViewController;
public AppDelegate(): base(new Game(new StaticsDatabase(),new StoreDatabase(),new InappPurchase(),new Social(),new MissionsDatabase()), getConfig()) {
}
internal static IOSApplicationConfiguration getConfig() {
IOSApplicationConfiguration config = new IOSApplicationConfiguration();
config.orientationLandscape = true;
config.orientationPortrait = false;
config.useAccelerometer = false;
config.useMonotouchOpenTK = true;
config.useObjectAL = true;
return config;
}
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
base.FinishedLaunching(app,options);
UIViewController controller = ((IOSApplication)Gdx.app).getUIViewController();
mainViewController = new MainViewController();
controller.View.Add(mainViewController.View);
return true;
}
private bool isGameCenterAPIAvailable()
{
return UIDevice.CurrentDevice.CheckSystemVersion (4, 1);
}
}
static void Main (string[] args)
{
UIApplication.Main (args, null, "AppDelegate");
}
}
}
And here is the superclass of that Main.cs : https://github.com/libgdx/libgdx/blob/master/backends/gdx-backend-iosmonotouch/src/com/badlogic/gdx/backends/ios/IOSApplication.java
I'm trying to use this https://github.com/xamarin/monotouch-samples/blob/master/GameCenterSample/GameCenterSample/MainViewController.cs example but i can't see any authenticate window in my game.I can see "Welcome back ,name" notification but after i log out from gamecenter app and reopen my game but i can't see any authentication window.
How can i fix it?
Thanks in advance
Just call this in FinishedLaunching:
if (!GKLocalPlayer.LocalPlayer.Authenticated) {
GKLocalPlayer.LocalPlayer.Authenticate (error => {
if (error != null)
Console.WriteLine("Error: " + error.LocalizedDescription);
});
}
This should display a Game Center "toast" saying "Welcome back, Player 1".
Here are some ideas if this doesn't work:
Make sure you have setup a new bundle id in the developer portal, and declare it in your Info.plist
Start filling out your app details in iTunes connect (Minimum is description, keywords, icon, 1 screenshot), and make sure to enable Game Center and add your new game to a group
Login with a test iTunes user in Game Center (create in ITC), or the login associated with your developer account
PS - I wouldn't worry about checking for iOS 4.1, just target iOS 5.0 and higher these days.