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

  • Question

  • User84570 posted

    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 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.

    Saturday, July 21, 2018 12:14 AM

Answers

  • User369979 posted

    If you want to use WKWebView instead of UIWebView, why not use Custom Renderer directly? Firstly, create a new class inherited from WebView in your PCL. And define a bindable property pointing what url should be loaded on iOS. Here is my custom WebView:

    public class MyWebView : WebView
    {
        public static readonly BindableProperty UrlProperty = BindableProperty.Create(
            propertyName: "Url",
            returnType: typeof(string),
            declaringType: typeof(MyWebView),
            defaultValue: default(string));
    
        public string Url
        {
            get { return (string)GetValue(UrlProperty); }
            set { SetValue(UrlProperty, value); }
        }
    }
    

    The custom renderer for this class on iOS:

    [assembly: ExportRenderer(typeof(MyWebView), typeof(MyWebViewRenderer))]
    namespace WKWebViewDemo.iOS
    {
        public class MyWebViewRenderer : ViewRenderer<MyWebView, WKWebView>
        {
            WKWebView _wkWebView;
            protected override void OnElementChanged(ElementChangedEventArgs<MyWebView> 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.Url)));
                }
            }
        }
    }
    

    At last you can use this custom control in the PCL's page, here is the usage:

    <StackLayout>
        <local:MyWebView Url="https://www.microsoft.com" VerticalOptions="FillAndExpand"/>
    </StackLayout>
    
    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Monday, July 23, 2018 8:20 AM

All replies

  • User369979 posted

    If you want to use WKWebView instead of UIWebView, why not use Custom Renderer directly? Firstly, create a new class inherited from WebView in your PCL. And define a bindable property pointing what url should be loaded on iOS. Here is my custom WebView:

    public class MyWebView : WebView
    {
        public static readonly BindableProperty UrlProperty = BindableProperty.Create(
            propertyName: "Url",
            returnType: typeof(string),
            declaringType: typeof(MyWebView),
            defaultValue: default(string));
    
        public string Url
        {
            get { return (string)GetValue(UrlProperty); }
            set { SetValue(UrlProperty, value); }
        }
    }
    

    The custom renderer for this class on iOS:

    [assembly: ExportRenderer(typeof(MyWebView), typeof(MyWebViewRenderer))]
    namespace WKWebViewDemo.iOS
    {
        public class MyWebViewRenderer : ViewRenderer<MyWebView, WKWebView>
        {
            WKWebView _wkWebView;
            protected override void OnElementChanged(ElementChangedEventArgs<MyWebView> 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.Url)));
                }
            }
        }
    }
    

    At last you can use this custom control in the PCL's page, here is the usage:

    <StackLayout>
        <local:MyWebView Url="https://www.microsoft.com" VerticalOptions="FillAndExpand"/>
    </StackLayout>
    
    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Monday, July 23, 2018 8:20 AM
  • User84570 posted

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

    Monday, July 23, 2018 5:58 PM
  • User356011 posted

    @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!

    Sunday, October 28, 2018 9:16 AM
  • User380614 posted

    @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"

    Tuesday, March 5, 2019 1:46 PM
  • User351573 posted

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

    Wednesday, October 23, 2019 11:28 AM
  • User387977 posted

    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 { 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:

    Wednesday, March 4, 2020 7:51 AM
  • User387977 posted

    step 3:

    * *

    Wednesday, March 4, 2020 7:53 AM
  • User389927 posted

    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);
            }
    
        }
    
    
    }
    

    }

    Wednesday, May 20, 2020 9:18 AM