Forum Xamarin Cross Platform with Xamarin

How does one implement WkWebView (iOS) in a cross-platform application?

I have a cross-platform mobile app (Android/iOS) which implements the generic WebView control. This works well for most circumstances, but some iOS users complain that, when attempting to load a certain resource-intensive web page, the app "goes black" and then focus returns to the Menu view. My suspicion is that the app is choking due to the amount of content and processing overhead of the web page, but frankly this is a blind guess and I don't have the resources (such as an iPhone at my disposal) in order to verify this. Using an iPhone simulator on a Mac does not reproduce the "black screen" issue.

Therefore, I am attempting to implement in parallel WkWebView for iOS devices at version 8.0 and above as this is presumably more performant and might alleviate the problem. It is just about working, but there seems to be a disconnect between the ViewController and ContentPage which is supposed to host the WkWebView control which I have been unable to rectify.

Below is the general implementation:

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class WkWebViewPage : ContentPage
{
    public WkWebViewPage (string url, string title)
    {
        InitializeComponent();

        Title = title;

        App.GetWkWebView(this, url);
    }
}

The markup for WkWebPageView has no inner content.

The method App.GetWkWebView is a delegate of type Action<Page, string> implemented as a static property in the main App class. This is assigned in the FinishedLaunching method of the AppDelegate class (iOS project) to a static method in the static class I am using to manage invoking the WkWebView. This class is implemented as such:

public static class WkWebViewController
{
    private static WKWebView _wkWebView;

    public static void GetWkWebView(Page parentView, string url)
    {
        if(_wkWebView == null)
        {

    // INSERT ATTEMPTED APPROACHES BELOW HERE

            var frame = view.Frame;

            var cgRect = new CoreGraphics.CGRect(frame.X, frame.Y, frame.Width, frame.Height);

            _wkWebView = new WKWebView(cgRect, new WKWebViewConfiguration());

            view.AddSubview(_wkWebView);

    // NavigationDelegate is a custom class; not germane to the issue
            _wkWebView.NavigationDelegate = new NavigationDelegate();
        }

        var nsUrl = new NSUrl(url);
        var request = new NSUrlRequest(nsUrl);

        _wkWebView.LoadRequest(request);
    }
}

Here is where the trouble begins. I have tried two approaches to obtaining the appropriate ViewController -- and more pertinently, the UIView object:

1)

            var renderer = Platform.GetRenderer(parentView);

            if (renderer == null)
            {
                renderer = Platform.CreateRenderer(parentView);
                Platform.SetRenderer(parentView, renderer);
            }

            var view = renderer.ViewController.View;

This results in:

I WOULD POST AN IMAGE HERE, BUT AM PREVENTED FROM DOING SO; THEREFORE, PLEASE USE YOUR IMAGINATION.

The content area is white/blank. The http request is submitted successfully as a 200 response is received. Note that the navigation bar above the content area properly displays.

2)

            var window = UIApplication.SharedApplication.KeyWindow;

            var vc = window.RootViewController;

            while (vc.PresentedViewController != null)
            {
                vc = vc.PresentedViewController;
            }

            var view = vc.View;

which results in:

I WOULD POST ANOTHER IMAGE HERE, BUT AM PREVENTED FROM DOING SO; THEREFORE, PLEASE USE YOUR IMAGINATION FOR A SECOND TIME.

In this case, the web page displays; however, the WkWebView control takes up the entire screen, obscuring the navigation bar (and seemingly the Status Bar).

Any suggestions would be greatly appreciated!

NOTE: Links to images for approaches 1 and 2 can be provided upon request.

Best Answer

Answers

  • tmccoidtmccoid USMember

    This is a simple, clear, and elegant solution. Thank you for your help!

  • mshwfmshwf EGMember ✭✭✭
    edited October 2018

    @LandLu Hi, I also need to implement the WKWebView as there are problems in rendering HTML with UIWebView, I don't want to create custom control in the shared project, I want to use the WebView directly and utilize the Source property where it can be an HTML or a URL, also I don't want to break the current code where the WebView is used heavily.
    I tried this:

     public class MyWebViewRenderer : ViewRenderer<WebView, WKWebView>
        {
            WKWebView _wkWebView;
    
            protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
            {
                base.OnElementChanged(e);
    
                if (Control == null)
                {
                    var config = new WKWebViewConfiguration();
                    _wkWebView = new WKWebView(Frame, config);
                    SetNativeControl(_wkWebView);
                }
                if (e.NewElement != null)
                {
                    Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Source)));
                }
            }
        }
    

    but new NSUrl(Element.Source) is not valid!

  • JohannesHJohannesH Member ✭✭
    edited March 2019

    @mshwf said:
    @LandLu Hi, I also need to implement the WKWebView as there are problems in rendering HTML with UIWebView, I don't want to create custom control in the shared project, I want to use the WebView directly and utilize the Source property where it can be an HTML or a URL, also I don't want to break the current code where the WebView is used heavily.
    I tried this:

     public class MyWebViewRenderer : ViewRenderer<WebView, WKWebView>
        {
            WKWebView _wkWebView;
    
            protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
            {
                base.OnElementChanged(e);
    
                if (Control == null)
                {
                    var config = new WKWebViewConfiguration();
                    _wkWebView = new WKWebView(Frame, config);
                    SetNativeControl(_wkWebView);
                }
                if (e.NewElement != null)
                {
                    Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Source)));
                }
            }
        }
    

    but new NSUrl(Element.Source) is not valid!

    @mshwf Change "Element.Source" to "(Element.Source as UrlWebViewSource).Url"

  • SreeeeSreeee INMember ✭✭✭✭✭

    @LandLu I tried your code but getting System.ArumentNullException.
    Screenshot

  • Amit_SopinAmit_Sopin Member ✭✭

    For WKWebView , Navigated and navigating methods

    step 1:
    public class ExtendedWebView: WebView
    {
    public ExtendedWebView()
    {
    }

        public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: "Uri",
           returnType: typeof(string), declaringType: typeof(ExtendedWebView),
           defaultValue: default(string));
    
        public string Uri
        {
            get { return (string)GetValue(UriProperty); }
            set { SetValue(UriProperty, value); }
        }
    
        public  event System.EventHandler LoadingStart;
        public event System.EventHandler LoadingFinished;
    
    
        public void InvokeCompleted()
        {
            if (this.LoadingFinished != null)
                this.LoadingFinished.Invoke(this, null);
        }
    
        public void InvokeStarted()
        {
            if (this.LoadingStart != null)
                this.LoadingStart.Invoke(this, null);
        }
    }
    

    .......................
    step -2

    namespace *** .iOS.Renderer
    {
    public class ExtendedWebViewRenderer : ViewRenderer<ExtendedWebView, WKWebView>
    {
    public ExtendedWebViewRenderer()
    {
    }
    WKWebView webView;

        protected override void OnElementChanged(ElementChangedEventArgs<ExtendedWebView> e)
        {
            base.OnElementChanged(e);
    
            if (Control == null)
            {
                webView = new WKWebView(Frame, new WKWebViewConfiguration() { MediaPlaybackRequiresUserAction = false });
                webView.NavigationDelegate  = new DisplayLinkWebViewDelegate(Element);
                SetNativeControl(webView);
            }
            if (e.NewElement != null)
            {
                Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
                webView.NavigationDelegate = new DisplayLinkWebViewDelegate(Element);
                SetNativeControl(webView);
            }
    
            }
    
    }
    
    public class DisplayLinkWebViewDelegate : WKNavigationDelegate
    {
        private ExtendedWebView element;
    
    
        public DisplayLinkWebViewDelegate(ExtendedWebView element)
        {
            this.element = element;
        }
    
        public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
        {
            element.InvokeCompleted();
            //base.DidFinishNavigation(webView, navigation);
        }
    
        public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigation navigation)
        {
            element.InvokeStarted();
         //   base.DidStartProvisionalNavigation(webView, navigation);
        }
    
        public override void DidFailNavigation(WKWebView webView, WKNavigation navigation, NSError error)
        {
         //   base.DidFailNavigation(webView, navigation, error);
        }
    
    
    }
    

    }

    step 3:

  • Amit_SopinAmit_Sopin Member ✭✭
    edited March 4

    step 3:

    ** **

  • LijuDanielLijuDaniel Member ✭✭

    POST Request Over Webview

    For WkWebview

    [assembly: ExportRenderer(typeof(PaymentWebview), typeof(PaymentWebViewRenderer))]
    namespace MMFInvestorApp.iOS.Utils
    {
    public class PaymentWebViewRenderer : WkWebViewRenderer
    {
    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
    base.OnElementChanged(e);

            if (NativeView != null)
            {
                var request = new NSMutableUrlRequest(new NSUrl(new NSString(paymentwebview.url))); //Your Url
                request.HttpMethod = "POST";
                request.Body = NSData.FromString(paymentwebview.data); //Data for POST
                request["Content-Length"] = req.Body.Length.ToString();
                request["Content-Type"] = "application/x-www-form-urlencoded charset=utf-8";
                LoadRequest(request);
            }
    
        }
    
    
    }
    

    }

    For UIWebview (Deprecated from April 2020

    [assembly: ExportRenderer(typeof(PaymentWebview), typeof(PaymentWebViewRenderer))]
    namespace MMFInvestorApp.iOS.Utils
    {
    public class PaymentWebViewRenderer : WebViewRenderer
    {
    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
    base.OnElementChanged(e);

            if (NativeView != null)
            {
                var paymentwebview = Element as PaymentWebview;
                var request = new NSMutableUrlRequest(new NSUrl(new NSString(paymentwebview.url)));//Your Url
                request.Body = paymentwebview.data; //Data for POST
                request.HttpMethod = "POST";
                LoadRequest(request);
            }
    
        }
    
    
    }
    

    }

Sign In or Register to comment.