I'm trying to load some webpages inside of WKWebView. I want to display a loading spinner on the screen when webview is loading and hide it once the page finishes loading (specifically when DidFinishNavigation gets called), but DidFinishNavigation never gets called when the page that is loading contains an iframe element in HTML, because of that, I'm not able to hide the loading spinner.
Please let me know why this happened and if there is any workarounds for this issue.
For example:
Web Code
<section id="Document">
<iframe src="SomeOtherPages"></iframe>
<div id="Loader"></div>
</section>
XAMARIN Code
public override void ViewDidLoad()
{
base.ViewDidLoad();
var userController = new WKUserContentController();
// add JS function to the document so we can call it from C#
userController.AddUserScript(
new WKUserScript(
new NSString(Global.MessageHandlerFunction),
WKUserScriptInjectionTime.AtDocumentEnd, false));
// register messageHandler 'native' that can be called with window.webkit.messageHandlers.native
userController.AddScriptMessageHandler(this, "native");
var config = new WKWebViewConfiguration { UserContentController = userController };
webView = new WKWebView(View.Frame, config) { WeakNavigationDelegate = this };
webView.AllowsBackForwardNavigationGestures = true;
webView.UIDelegate = this;
webView.NavigationDelegate = this;
View.AddSubview(webView);
this.loadingOverlay = new LoadingOverlay(View.Frame);
View.AddSubview(loadingOverlay);
webView.LoadRequest(new NSUrlRequest(new NSUrl(defaultUrl)));
}
[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
public void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action<WKNavigationActionPolicy> decisionHandler)
{
decisionHandler(WKNavigationActionPolicy.Allow);
Console.WriteLine("DecidePolicy " + webView.Url);
SetLoadingOverlay(false);
}
public void SetLoadingOverlay(bool hide){
InvokeOnMainThread(()=>{
UIView.Animate(0.5, () => { loadingOverlay.Hidden = hide; });
});
}
[Export("webView:didFinishNavigation:")]
public void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
Console.WriteLine("Finish Loading...");
SetLoadingOverlay(true);
}
[Export("webView:didFailNavigation:withError:")]
public void DidFailNavigation(WKWebView webView, WKNavigation navigation, NSError error)
{
Console.WriteLine("Failed Loading...");
SetLoadingOverlay(true);
}
[Export("webView:didFailProvisionalNavigation:withError:")]
public void DidFailProvisionalNavigation(WKWebView webView, WKNavigation navigation, NSError error)
{
Console.WriteLine("Failed Loading...");
SetLoadingOverlay(true);
}
Related
Can I use both of them in a project?
I need to override WKUIDelegate's CreateWebView method in order to open target=_blank links:
public override WKWebView CreateWebView(WKWebView webView, WKWebViewConfiguration configuration, WKNavigationAction navigationAction, WKWindowFeatures windowFeatures)
{
var url = navigationAction.Request.Url;
if (navigationAction.TargetFrame == null)
{
webView.LoadRequest(navigationAction.Request);
}
return null;
}
When I use WKUIDelegate in a demo it works (opens target _blank). But in real project they used WKNavigationDelegate too. And applying WKUIDelegate CreateWebView doesn't work.
OnElementChange in the renderer is like this:
var config = new WKWebViewConfiguration { };
webView = new WKWebView(Frame, config);
// Set the delegate here
webView = new WKWebView(this.Frame, new WKWebViewConfiguration());
webView.ScrollView.ScrollEnabled = true;
webView.ScrollView.Bounces = true;
webView.NavigationDelegate = new DisplayLinkWebViewDelegate();
webView.UIDelegate = MyWkWebViewDelegate();
SetNativeControl(webView);
WKNavigationDelegate : It helps you implement custom behaviors that are triggered during a web view's process of accepting, loading, and completing a navigation request.
And the WKUIDelegate class provides methods for presenting native user interface elements on behalf of a webpage.
The webpage here is not the webview ,but the html which been loaded on webview.
As we can see in following image
The method in WKUIDelegate are all associated with JS.
For more details about the two protocols you can check https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc
and
https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc
if you want to do something when the webview finished loading, you can implement the method DidFinishNavigation in WKNavigationDelegate .
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
if(!webView.IsLoading)
{
// do some thing you want
}
}
trying to learn more about Tabbed Pages with i've built a very simple App containing three content Pages with a code like this:
public class Page1 : ContentPage
{
public Page1()
{
Content = new StackLayout
{
Children = {
new Label { Text = "Hello Page1" }
}
};
}
protected override void OnAppearing()
{
base.OnAppearing();
System.Diagnostics.Debug.WriteLine("Page 1 On Appearing");
}
protected override void OnDisappearing()
{
base.OnDisappearing();
System.Diagnostics.Debug.WriteLine("Page 1 Disappearing");
}
}
The Main Page looks like this:
public class MainPage : TabbedPage
{
public MainPage()
{
var page1 = new Page1();
page1.Title = "Page1";
var page2 = new Page2();
page2.Title = "Page2";
var page3 = new Page3();
page3.Title = "Page3";
Children.Add(page1);
Children.Add(page2);
Children.Add(page3);
}
}
Now when i click on a new tab, the OnDisappearing() method of the old tab is called, as well as the OnAppearing() method of new tab, BUT the content of the new page is not shown. It remains the content of the old page.
To show the content of the new page i have to click again on the tab.
Does anybody has experienced this kind of behaviour?
Best regards,
Marco
I want to open a UIViewController from a content page and this code helps me in doing this
UIWindow Window = new UIWindow(UIScreen.MainScreen.Bounds);
var cvc = new ScanningPage();
var navController = new UINavigationController(cvc);
Window.RootViewController = navController;
Window.MakeKeyAndVisible();
but I also want a Back button on that Controller Page which navigate me back to that content Page.
this.NavigationController.PopViewController(true);
or
this.DismissViewController(true,null);
not working in this case.
I would recommend not creating a new Window and a UINavigationController...
Xamarin.Forms is contained in a single VC in the first Window, so you can obtain that view controller via:
UIApplication.SharedApplication.Windows[0].RootViewController;
So as an example Dependency service that presents and dismisses a VC (either from Forms or the view controller), you can do something like this.
Dependency interface:
public interface IDynamicVC
{
void Show();
void Dismiss();
}
iOS Dependency implementation
public class DynamicVC : IDynamicVC
{
UIViewController vc;
public void Show()
{
if (vc != null) throw new Exception("DynamicVC already showing");
vc = new UIViewController();
var button = new UIButton(new CGRect(100, 100, 200, 200));
button.SetTitle("Back", UIControlState.Normal);
button.BackgroundColor = UIColor.Red;
button.TouchUpInside += (object sender, EventArgs e) =>
{
Dismiss();
};
vc.Add(button);
var rootVC = UIApplication.SharedApplication.Windows[0].RootViewController;
rootVC.PresentViewController(vc, true, () => { });
}
public void Dismiss()
{
vc?.DismissViewController(true, () =>
{
vc.Dispose();
vc = null;
});
}
}
I need to print a picture on client side. I used this as a template. My PrintUI looks like this:
#Override
protected void init(VaadinRequest request) {
Item item = ..get item ..
StreamResource imageStream = ... build image dynamically ...
Image image = new Image(item.getName(), imageStream);
image.setWidth("100%");
setContent(image);
setWidth("100%");
// Print automatically when the window opens
JavaScript.getCurrent().execute("setTimeout(function() {print(); self.close();}, 0);");
}
This works so far in IE but in chrome it opens the printing preview showing an empty page. The problem is that the image is loaded in some way that chrome does not wait for it and starts the printing preview immideatly.
To verify this, I tried: (setting a 5sec timeout)
JavaScript.getCurrent().execute("setTimeout(function() {print(); self.close();}, 0);");
Then it works in IE and Chrome, but its of course an ugly hack, and if the connection is slower than 5sec, then again it will fail.
In pure JS it would work like this, but Im not sure how to reference the element from vaadin in cient-side js. Any ideas?
You can use AbstractJavascriptExtension.
Example extension class:
#JavaScript({ "vaadin://scripts/connector/wait_for_image_load_connector.js" })
public class WaitForImageLoadExtension extends AbstractJavaScriptExtension {
private List<ImageLoadedListener> imageLoadedListeners = new ArrayList<>();
public interface ImageLoadedListener {
void onImageLoaded();
}
public void extend(Image image) {
super.extend(image);
addFunction("onImageLoaded", new JavaScriptFunction() {
#Override
public void call(JsonArray arguments) {
for (ImageLoadedListener imageLoadedListener : imageLoadedListeners) {
if (imageLoadedListener != null) {
imageLoadedListener.onImageLoaded();
}
}
}
});
}
public void addImageLoadedListener(ImageLoadedListener listener) {
imageLoadedListeners.add(listener);
}
}
and javascript connector (placed in wait_for_image_load_connector.js) with the waiting method you have linked:
window.your_package_WaitForImageLoadExtension = function() {
var connectorId = this.getParentId();
var img = this.getElement(connectorId);
if (img.complete) {
this.onImageLoaded();
} else {
img.addEventListener('load', this.onImageLoaded)
img.addEventListener('error', function() {
alert('error');
})
}
}
Then you can do something like that:
Image image = new Image(item.getName(), imageStream);
WaitForImageLoadExtension ext = new WaitForImageLoadExtension();
ext.extend(image);
ext.addImageLoadedListener(new ImageLoadedListener() {
#Override
public void onImageLoaded() {
JavaScript.eval("print()");
}
});
In your case, when calling print() is the only thing you want to do after the image is loaded, you can also do it without server-side listener by just calling it in the connector:
if (img.complete) {
print();
} else {
img.addEventListener('load', print)
img.addEventListener('error', function() {
alert('error');
})
}
In my Xamarin forms application I want to show a confirmation message when user clicks the back button from Main-page. Is there any way to achieve this?
I overrided the OnBackButtonPressed method in my MainPage. But still the app is closing while back key press. Here is my code
protected override bool OnBackButtonPressed ()
{
//return base.OnBackButtonPressed ();
return false;
}
You can override OnBackButtonPressed for any Xamarin.Form Page. But it only will work for the physical button in Android and Windows Phone devices.
protected override bool OnBackButtonPressed () {
DisplayAlert("title","message","ok");
return true;
}
For the virtual one, you will need to create CustomRenderers and to intercept the click handler. In iOS it can be tricky because the user can go back doing other actions (e.g. the swipe gesture). Once you intercept it you just need to create your Confirmation Message (which I assume that you know how to do it).
For iOS you can do something like this:
[assembly: ExportRenderer (typeof (YourPage), typeof (YourPageRenderer))]
namespace YourNamespace {
public class YourPageRenderer : PageRenderer {
public override void ViewWillAppear (bool animated) {
base.ViewWillAppear (animated);
Action goBack = () => page.DisplayAlert("title","message","ok");
var backButton = new NavBackButton (goBack);
navigationItem.LeftBarButtonItem = new UIBarButtonItem (backButton);
}
}
public class NavBackButton : UIView {
public NavBackButton (Action onButtonPressed) {
SetButton (onButtonPressed);
}
UILabel text;
UIImageView arrow;
void SetButton(Action onButtonPressed){
arrow = new UIImageView(new CGRect(-25,0, 50, 50)) {
Image = new UIImage("Images/back").ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
};
arrow.TintColor = Colors.DarkGreen.ToUIColor ();
text = new UILabel(new CGRect(arrow.Frame.Width + arrow.Frame.X -15, arrow.Frame.Height /2 - 10, 40, 20)) { Text = "Back" };
text.TextColor = Colors.DarkGreen.ToUIColor ();
Frame = new CGRect(0,0,text.Frame.Size.Width + arrow.Frame.Width, arrow.Frame.Height);
AddSubviews (new UIView[] { arrow, text });
var tapGesture = new UITapGestureRecognizer (onButtonPressed);
AddGestureRecognizer (tapGesture);
}
public override void TouchesBegan (Foundation.NSSet touches, UIEvent evt) {
base.TouchesBegan (touches, evt);
text.TextColor = UIColor.YourColor;
arrow.TintColor = UIColor.YourColor;
}
public override void TouchesEnded (Foundation.NSSet touches, UIEvent evt){
base.TouchesEnded (touches, evt);
arrow.TintColor = UIColor.YourColor;
text.TextColor = UIColor.YourColor;
}
}
}
PS You will need to provide an arrow image ("Images/back")