I use a base content page for all pages in my app. I want to add a gradient to the content page so I created LocalGradientContentPage and had BaseContentPage inherit LocalGradientContentPage. Each platform has a custom renderer and everything works perfectly on Android. The android custom renderer is called when BaseContentPage is used. The issue is with iOS. iOS never calls the custom renderer when using BaseContentPage. It only calls the custom renderer if I use LocalGradientContentPage directly. All my classes follow.
LocalGradientContentPage.cs
namespace MyNamespace.Forms
{
public class LocalGradientContentPage : ContentPage
{
public static readonly BindableProperty EndColorProperty = BindableProperty.Create(
nameof(EndColor),
typeof(Color),
typeof(LocalGradientContentPage),
Color.White);
public Color EndColor
{
get => (Color)GetValue(EndColorProperty);
set => SetValue(EndColorProperty, value);
}
public static readonly BindableProperty StartColorProperty = BindableProperty.Create(
nameof(StartColor),
typeof(Color),
typeof(LocalGradientContentPage),
Color.Black);
public Color StartColor
{
get => (Color)GetValue(StartColorProperty);
set => SetValue(StartColorProperty, value);
}
}
}
Android: LocalGradientContentPageRenderer.cs
[assembly: ExportRenderer(typeof(LocalGradientContentPage),typeof(LocalGradientContentPageRenderer))]
namespace MyNamespace.DroidRenderers
{
public class LocalGradientContentPageRenderer : PageRenderer
{
public LocalGradientContentPageRenderer(Context context) : base(context)
{
}
private Xamarin.Forms.Color StartColor { get; set; }
private Xamarin.Forms.Color EndColor { get; set; }
protected override void DispatchDraw(Canvas canvas)
{
var gradient = new LinearGradient(0, 0, 0, Height,
StartColor.ToAndroid(),
EndColor.ToAndroid(),
Shader.TileMode.Mirror);
var paint = new Paint()
{
Dither = true,
};
paint.SetShader(gradient);
canvas.DrawPaint(paint);
base.DispatchDraw(canvas);
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
if (!(e.NewElement is LocalGradientContentPage page)) return;
StartColor = page.StartColor;
EndColor = page.EndColor;
}
}
}
iOS:LocalGradientContentPage.cs
[assembly: ExportRenderer(typeof(LocalGradientContentPage), typeof(LocalGradientContentPageRenderer))]
namespace MyNamespace.iOSRenderers
{
public class LocalGradientContentPageRenderer : PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement != null) return;
if (e.NewElement is LocalGradientContentPage page)
{
var gradientLayer = new CAGradientLayer
{
Frame = View.Bounds,
Colors = new CGColor[] { page.StartColor.ToCGColor(), page.EndColor.ToCGColor() }
};
View.Layer.InsertSublayer(gradientLayer, 0);
}
}
}
}
BaseContentPage.cs
public partial class BaseContentPage : LocalGradientContentPage
{
public BaseContentPage() : base()
{
InitializeComponent();
}
}
Using like this works:
<views:LocalGradientContentPage
StartColor="Blue"
EndColor="HotPink">
</views:LocalGradientContentPage>
Using like this does not work and the iOS LocalGradientContentPageRenderer is never called:
<views:BaseContentPage
StartColor="Blue"
EndColor="HotPink">
</views:LocalGradientContentPage>
I've do not have linking but just in case I did instantiate LocalGradientContentPage in AppDelegate.cs. It does hit the constructor when instantiated this way.
_ = new LocalGradientContentPage();
Also, as stated earlier, this is working fine in Android and calls the custom renderer when BaseContentPage is used.
I'm really at a loss as to why this isn't working. Any help would be appreciated.
Well, I use you code and add the views:BaseContentPage to a tabbedPage, it works well.
I use it like:
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:App629"
mc:Ignorable="d"
x:Class="App629.TabbedPage1">
<!--Pages can be added as references or inline-->
<views:BaseContentPage
StartColor="Blue"
EndColor="HotPink"/>
<ContentPage Title="Tab 2" />
<ContentPage Title="Tab 3" />
<views:BaseContentPage StartColor="Red" EndColor="HotPink">
</views:BaseContentPage>
</TabbedPage>
Some suggestions:
I'm using the latest Xamarin.forms version.
Check if there are any other codes in the BaseContentPage that affect the custom renderer?
Try the code as me to check if it works?
Add a breakPoint there in the custom renderer to check if it hit the code.
Related
I develop Xamarin Forms app. And I use QLPreviewController for ios. I want to close copy text/text share, if document has text. Is it possible and how? I searched it as native. Some sources redirect me to UIDocumentInteractionControllerDelegate. But I didn't understand how can i do it.
Thanks in advance.
Edit:
My UIDocumentInteractionController implementation:
var firstController = UIApplication.SharedApplication.KeyWindow.RootViewController.ChildViewControllers.First().ChildViewControllers.Last().ChildViewControllers.First();
var navcontroller = firstController as UINavigationController;
var uidic = UIDocumentInteractionController.FromUrl(new NSUrl(file, true));
uidic.Delegate = new DocInteractionController(navcontroller);
uidic.PresentPreview(true);
public class DocInteractionController : UIDocumentInteractionControllerDelegate
{
UINavigationController navigationController;
public DocInteractionController(UINavigationController _navigationController)
{
navigationController = _navigationController;
}
}
In your case it would be better to use WebView to preview files like pdf .
In forms
Create a custom webview
public class MyWebView : WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(MyWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
in Android project
using Android.Content;
using Android.Net.Http;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Webkit;
using Android.Widget;
using App32;
using App32.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(MyWebView), typeof(CustomWebViewRenderer))]
namespace App32.Droid
{
public class CustomWebViewRenderer : WebViewRenderer
{
Context _context;
public CustomWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Android.Webkit.WebView web_view = new Android.Webkit.WebView(_context);
web_view.LoadUrl("https://drive.google.com/viewerng/viewer?embedded=true&url="+((MyWebView)Element).Uri);
SetNativeControl(web_view);
Control.Settings.JavaScriptEnabled = true;
}
}
}
}
in iOS project
using System;
using App32;
using App32.iOS;
using Foundation;
using ObjCRuntime;
using UIKit;
using WebKit;
using Xamarin.Forms.Platform.iOS;
[assembly: Xamarin.Forms.ExportRenderer(typeof(MyWebView), typeof(MyWebViewRenderer))]
namespace App32.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)
{
var webview = Element as MyWebView;
var url = webview.Uri;
Control.LoadRequest(new NSUrlRequest(new NSUrl("https://drive.google.com/viewerng/viewer?embedded=true&url=" + webview.Uri)));
}
}
}
}
in xaml
Now you can use it in xaml like (you could open a new ContentPage that contains the WebView)
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<local:MyWebView Uri="https://www.pdfpdf.com/samples/Sample1.PDF"/>
</StackLayout>
public class ImageAdapter : BaseAdapter
{
Context context;
public ImageAdapter (Context conn)
{
context = conn;
}
public override int Count { get { return thumbIds.Length; } }
public override Java.Lang.Object GetItem (int position)
{
return null;
}
public override long GetItemId (int position)
{
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public override View GetView (int position, View convertView, ViewGroup parent)
{
ImageView pic = new ImageView (context);
pic.SetImageResource (thumbIds[position]);
pic.LayoutParameters = new Gallery.LayoutParams (500, 500);
pic.SetScaleType (ImageView.ScaleType.FitXy);
return pic;
}
// references to our images
int[] thumbIds = {
Resource.Drawable.image_1,
Resource.Drawable.image_2,
Resource.Drawable.image_3,
Resource.Drawable.image_4,
Resource.Drawable.image_5,
Resource.Drawable.image_6,
Resource.Drawable.image_7
};
}
How to remove item from array
https://learn.microsoft.com/en-us/xamarin/android/user-interface/controls/gallery
Gallery
Do you want to achieve the result like following GIF?
I advice your to use List<int> to replace of int[], then you can add a method called removeItem in the ImageAdapter like following code.
public void removeItem(int numToRemove)
{
if (numToRemove < thumbIds.Count)
{
thumbIds.RemoveAt(numToRemove);
this.NotifyDataSetChanged();
}
}
Here is code about ImageAdapter.cs
using Android.Views;
using Android.Widget;
using Java.Lang;
using System.Collections.Generic;
using System.Linq;
namespace App25
{
internal class ImageAdapter : BaseAdapter
{
private MainActivity mainActivity;
public ImageAdapter(MainActivity mainActivity)
{
this.mainActivity = mainActivity;
}
public override int Count { get { return thumbIds.Count; } }
public override Java.Lang.Object GetItem(int position)
{
return thumbIds[position];
}
public override long GetItemId(int position)
{
return position;
}
// create a new ImageView for each item referenced by the Adapter
public override View GetView(int position, View convertView, ViewGroup parent)
{
ImageView pic = new ImageView(mainActivity);
pic.SetImageResource(thumbIds[position]);
pic.LayoutParameters = new Gallery.LayoutParams(500, 500);
pic.SetScaleType(ImageView.ScaleType.FitXy);
return pic;
}
public void removeItem(int numToRemove)
{
if (numToRemove < thumbIds.Count)
{
thumbIds.RemoveAt(numToRemove);
this.NotifyDataSetChanged();
}
}
// references to our images
List<int> thumbIds = new List<int> {
Resource.Drawable.faded_div,
Resource.Drawable.icon,
Resource.Drawable.faded_div1,
Resource.Drawable.icon1,
Resource.Drawable.faded_div,
Resource.Drawable.icon2,
Resource.Drawable.faded_div
};
}
}
Here is MainActivity.cs
[Activity(Label = "#string/app_name", Theme = "#style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
ImageAdapter imageAdapter;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
Gallery gallery = (Gallery)FindViewById<Gallery>(Resource.Id.gallery1);
Button button1 = FindViewById<Button>(Resource.Id.button1);
imageAdapter = new ImageAdapter(this);
gallery.Adapter = imageAdapter;
button1.Click += Button1_Click;
}
int removeItem = 0;
private void Button1_Click(object sender, System.EventArgs e)
{
imageAdapter.removeItem(removeItem);
}
}
Here is my activity_main.xml.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="remove"
android:id="#+id/button1"
/>
<Gallery
android:id="#+id/gallery1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
=============Update==================
If you want to achieve the long click ,push alert, then delete the item, you can refer to achieve a longclick event for your gallery like following code.
gallery.ItemLongClick += Gallery_ItemLongClick;
private void Gallery_ItemLongClick(object sender, AdapterView.ItemLongClickEventArgs e)
{
// throw new System.NotImplementedException();
Android.App.AlertDialog.Builder dialog = new Android.App.AlertDialog.Builder(this);
Android.App.AlertDialog alert = dialog.Create();
alert.SetTitle("Title");
alert.SetMessage("Do you want to remove this select item");
alert.SetButton("OK", (c, ev) =>
{
// Ok button click task
imageAdapter.removeItem(e.Position);
});
alert.Show();
}
Here is my running GIF.
I am using custom TabbedPage in my project for showing a badges(count) in Tab bar. I am using the below code for showing a custom tab bar but it is always returning null value in CustomTabbedPageRenderer.cs class OnWindowVisibilityChanged method's activity.ActionBar. I have tried the many workaround like changing the Theme as Theme.AppCompat.Light.DarkActionBar and added the below line in Window.RequestFeature(WindowFeatures.ActionBar); MainActivity.cs , but unfortunately these didn't help me.
MainActivity.cs
namespace Bakery.Droid
{
[Activity(Label = "Bakery.Droid", Icon = "#mipmap/icon_launcher", Theme = "#style/MyTheme", MainLauncher = false, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, ScreenOrientation = ScreenOrientation.Portrait)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
}
}
}
Home.cs
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace Bakery
{
public class Home : CustomTabPage
{
public Home()
{
NavigationPage.SetHasNavigationBar(this, false);
var burger = new NavigationPage(new Burger());
burger.Icon = "burger.png";
burger.Title = "Burger";
var sandwich = new NavigationPage(new Sandwich());
sandwich.Icon = "sandwich.png";
sandwich.Title = "Sandwich";
var pizza = new NavigationPage(new Pizza());
pizza.Icon = "pizza.png";
pizza.Title = "Pizza";
var roll = new NavigationPage(new Roll());
roll.Icon = "roll.png";
roll.Title = "Roll";
//adding childrens into the tab
Children.Clear();
Children.Add(burger);
Children.Add(sandwich);
Children.Add(pizza);
Children.Add(roll);
}
protected override void OnAppearing()
{
base.OnAppearing();
}
}
}
CustomTabPage.cs
using System;
using Xamarin.Forms;
namespace Bakery
{
public class CustomTabPage : TabbedPage
{
}
}
styles.xml
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<style name="MyTheme" parent="MyTheme.Base">
</style>
<style name="MyTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">true</item>
<item name="windowActionModeOverlay">true</item>
</style>
</resources>
CustomTabbedPageRenderer.cs
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedPageRenderer))]
namespace Bakery.Droid
{
public class CustomTabbedPageRenderer : TabbedRenderer
{
Activity activity;
List<string> filenames;
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
filenames = e.NewElement.Children.Select(t => t.Icon.File).ToList();
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
activity = this.Context as Activity;
}
protected override void OnWindowVisibilityChanged(Android.Views.ViewStates visibility)
{
try
{
base.OnWindowVisibilityChanged(visibility);
var actionBar = activity.ActionBar;
var colorDrawable = new ColorDrawable(Android.Graphics.Color.Yellow);
actionBar.SetStackedBackgroundDrawable(colorDrawable);
System.Diagnostics.Debug.WriteLine("Onwindow visible");
ActionBarTabsSetup(actionBar);
}
catch (Exception Exception)
{
System.Diagnostics.Debug.WriteLine("Exception: " + Exception.ToString());
}
}
void ActionBarTabsSetup(ActionBar actionBar)
{
for (var i = 0; i < actionBar.NavigationItemCount; ++i)
{
var tab = actionBar.GetTabAt(i);
var id = GetImageFromFilename(i);
if (id != 0)
TabSetup(tab, id);
}
}
void TabSetup(ActionBar.Tab tab, int resID)
{
var relLay = new Android.Widget.RelativeLayout(activity)
{
LayoutParameters = new LayoutParams(LayoutParams.WrapContent, 180)
};
var linLay = new LinearLayout(activity)
{
LayoutParameters = new LayoutParams(LayoutParams.WrapContent, 180),
Orientation = Orientation.Vertical,
};
linLay.SetHorizontalGravity(Android.Views.GravityFlags.Center);
var imageView = new ImageView(activity);
imageView.SetImageResource(resID);
imageView.SetPadding(-35, 4, -35, 0);
imageView.SetMinimumWidth(60);
var textView = new TextView(activity)
{
Text = tab.Text
};
linLay.AddView(imageView);
linLay.AddView(textView);
relLay.AddView(linLay);
var badgeView = new TextView(activity)
{
Text = "2"
};
var badgeImageView = new ImageView(activity);
badgeImageView.SetImageResource(Resource.Drawable.red);
badgeImageView.SetMinimumWidth(5);
badgeImageView.SetMinimumHeight(5);
badgeImageView.SetPadding(77, 5, 0, 0);
badgeView.SetPadding(85, 0, 0, 0);
relLay.AddView(badgeImageView);
relLay.AddView(badgeView);
tab.SetCustomView(relLay);
}
int GetImageFromFilename(int n)
{
var filename = filenames[n].Split('.');
var id = Resources.GetIdentifier(filename[0], "drawable", activity.PackageName);
return id;
}
}
}
I am using the below code for showing a custom tab bar but it is always returning null value in CustomTabbedPageRenderer.cs class OnWindowVisibilityChanged method's activity.ActionBar.
The ActionBar is null because it is never set in the activity. You need to set the actionbar first:
In MainActivity.cs Set the SupportActionBar like below:
//Create your own theme "MyTheme"
[Activity(Label = "CustomTabbedPageDemo", Icon = "#drawable/icon", Theme = "#style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
//Get the ActionBar Layout in Resouce\layout\Toolbar.axml
var toolbar=(Toolbar)LayoutInflater.Inflate(Resource.Layout.Toolbar, null);
//Set the Support ActionBar
SetSupportActionBar(toolbar);
SupportActionBar.Title = "My ActionBar";
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
}
}
Create your own theme to replace the default one. Xamarin.Forms requires theme inherited from Theme.AppCompat.XXX.XXX:
<style name="MyTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="colorPrimary">#5A8622</item>
</style>
Basic Toolbar.axml example:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
Modify your CustomTabPageRenderer.cs change the namespace of ActionBar from Android.App to using Android.Support.V7.App and retrieve the ActionBar through activity.SupportActionBar:
using Android.Support.V7.App;
...
protected override void OnWindowVisibilityChanged(Android.Views.ViewStates visibility)
{
try
{
base.OnWindowVisibilityChanged(visibility);
//get the support actionbar
var actionBar = activity.SupportActionBar;
var colorDrawable = new ColorDrawable(Android.Graphics.Color.Yellow);
actionBar.SetStackedBackgroundDrawable(colorDrawable);
System.Diagnostics.Debug.WriteLine("Onwindow visible");
ActionBarTabsSetup(actionBar);
}
catch (Exception Exception)
{
System.Diagnostics.Debug.WriteLine("Exception: " + Exception.ToString());
}
}
In the past i've written a small renderer for buttons to maintain a padding property on my forms element. Lately it stopped working and while debugging i noticed it says unknown member all of a sudden (which would explain why it has no effect anymore)
[assembly: ExportRenderer(typeof(EnhancedButton), typeof(EnhancedButtonRenderer))]
namespace MyNamespace
{
public class EnhancedButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
UpdatePadding();
}
private void UpdatePadding()
{
var element = this.Element as EnhancedButton;
if (element != null && this.Control != null)
{
this.Control.ContentEdgeInsets = new UIEdgeInsets(
(int)element.Padding.Top,
(int)element.Padding.Left,
(int)element.Padding.Bottom,
(int)element.Padding.Right
);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(EnhancedButton.Padding))
{
UpdatePadding();
}
}
}
}
pcl:
public class EnhancedButton : Button
{
#region Padding
public static BindableProperty PaddingProperty = BindableProperty.Create<EnhancedButton, Thickness>(d => d.Padding, default(Thickness));
public Thickness Padding
{
get { return (Thickness) GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
#endregion Padding
}
Is anyone aware of a workaround? Has the support of this property been canceled on ios side?
Xamarin.Forms is 2.1.0.6524. If i recall correctly it worked just fine a couple versions ago.
I am developing an App using Xamarin.Forms for listing the news from different sources. I use a webView to open the link corresponding to the news. But I want to show the progress while loading the webpage into web view, like the progress bar on Safari App. For this I have used the ProgressBar element like this:
<StackLayout>
<!-- WebView needs to be given height and width request within layouts to render. -->
<ProgressBar Progress ="" HorizontalOptions="FillAndExpand" x:Name="progress"/>
<WebView x:Name="webView"
HeightRequest="1000"
WidthRequest="1000"
VerticalOptions= "FillAndExpand"
Navigating="webOnNavigating"
Navigated="webOnEndNavigating"/>
</StackLayout>
and in the code I have used
void webOnNavigating (object sender, WebNavigatingEventArgs e)
{
progress.IsVisible = true;
}
void webOnEndNavigating (object sender, WebNavigatedEventArgs e)
{
progress.IsVisible = false;
}
But I want to show also the progress of loading the data, not just an indication that is loading and load. I want the user to know that the data are loading. Is there a way to achieve this.
The implementations should be platform specific via custom renders. Luckily this topics has been discussed already for different platforms here on SO.
The Android version based on this thread:
[assembly: ExportRenderer(typeof(WebView), typeof(GenericWebViewRenderer))]
namespace WebViewWithProgressBar.Droid
{
public class GenericWebViewRenderer : WebViewRenderer
{
Context ctx;
public GenericWebViewRenderer(Context context) : base(context)
{
ctx = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var progressBar = new Android.Widget.ProgressBar(ctx, null, Android.Resource.Attribute.ProgressBarStyleHorizontal);
Control.SetWebChromeClient(new MyWebChromeClient(progressBar));
Control.AddView(progressBar);
}
class MyWebChromeClient : Android.Webkit.WebChromeClient
{
Android.Widget.ProgressBar progressBar;
public MyWebChromeClient(Android.Widget.ProgressBar progressBar)
{
this.progressBar = progressBar;
}
public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
{
progressBar.SetProgress(newProgress, true);
}
}
}
}
On iOS it is a bit trickier, here is a very simple mock that does it job pretty well:
[assembly: ExportRenderer(typeof(WebView), typeof(GenericWebViewRenderer))]
namespace WebViewWithProgressBar.iOS
{
public class GenericWebViewRenderer : ViewRenderer<WebView, UIWebView>
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var progressBar = new UIProgressView(UIProgressViewStyle.Bar);
progressBar.TintColor = UIColor.Green;
progressBar.TrackTintColor = UIColor.Black;
progressBar.ProgressTintColor = UIColor.Red;
var webView = new UIWebView(Frame);
webView.AddSubview(progressBar);
SetNativeControl(webView);
Control.Delegate = new MyUIWebViewDelegate(progressBar);
webView.LoadRequest(new NSUrlRequest(new NSUrl("https://google.com")));
}
}
class MyUIWebViewDelegate : UIWebViewDelegate
{
UIProgressView progressBar { get; }
public MyUIWebViewDelegate(UIProgressView progressBar)
{
this.progressBar = progressBar;
}
public override void LoadStarted(UIWebView webView)
{
progressBar.SetProgress(0.1f, false);
}
public override void LoadingFinished(UIWebView webView)
{
progressBar.SetProgress(1.0f, true);
}
public override void LoadFailed(UIWebView webView, NSError error)
{
// TODO:
}
}
}
}
For more details please check here.
P.S.: This code examples are available on github.