I'm building a Xamarin app that display a webview; currenlty I have my splash screen displayed when the application starts using the code on MainActivity.cs:
[Activity(Label = "App.Xamarin",
Icon = "#mipmap/icon",
Theme = "#style/Theme.Splash",
MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
Now I need to display the same splash in my webviewrenderer when the webvieb starts/stop to load its content, so I have my own WebViewClient:
internal class CusViewClient : WebViewClient
{
string _javascript;
public SistemiWebViewClient(string javascript)
{
_javascript = javascript;
}
public override void OnPageStarted(Android.Webkit.WebView view, string url, Android.Graphics.Bitmap favicon)
{
base.OnPageStarted(view, url, favicon);
view.Visibility = ViewStates.Invisible;
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
view.Visibility = ViewStates.Visible;
}
}
but I can't find the correct way to display the splash.
I ended up using a progress-bar instead of splash screen image to display the progress loading of the page in the chrome client, the code is the following:
public class CusWebChromeClient : WebChromeClient
{
Android.Widget.ProgressBar progressBar;
public SistemiWebChromeClient(Android.Widget.ProgressBar progressBar)
{
this.progressBar = progressBar;
}
public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
{
if (newProgress < 100 && progressBar.Visibility == ViewStates.Gone)
{
progressBar.Visibility = ViewStates.Visible;
}
progressBar.SetProgress(newProgress, true);
if (newProgress == 100)
{
progressBar.Visibility = ViewStates.Gone;
}
}
}
Related
Previously I was able to use TabbedRenderer to stylize the tabbed bar More menu page by implementing CustomMoreNavigationControllerDelegate as so...
public class ExtendedTabbedPageRenderer : TabbedRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
MoreNavigationController.Delegate = new CustomMoreNavigationControllerDelegate();
}
}
}
I'm now using Shell navigation instead, and so how can I assign/implement this class using ShellRenderer (since there is no OnElementChanged override)?
My More menu styling class
internal class CustomMoreNavigationControllerDelegate : UINavigationControllerDelegate
{
public override void WillShowViewController(UINavigationController navigationController, UIViewController viewController, bool animated)
{
viewController.NavigationController.NavigationBarHidden = false;
UILabel titleLabel = new UILabel();
if (viewController.NavigationItem.Title != "More")
{
titleLabel.Text = viewController.NavigationItem.Title;
}
else
{
titleLabel.Text = "More";
}
titleLabel.TextColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? UIColor.White : UIColor.Black;
viewController.NavigationItem.TitleView = titleLabel;
viewController.View.BackgroundColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? ExtendedTabbedPageRenderer.iOSDarkPageBackgroundColor : ExtendedTabbedPageRenderer.iOSLightPageBackgroundColor;
viewController.View.TintColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? UIColor.White : UIColor.Black;
if (viewController.View is UITableView tableView)
{
tableView.SeparatorColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? UIColor.White : UIColor.Black;
foreach (var cell in tableView.VisibleCells)
{
cell.BackgroundColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? ExtendedTabbedPageRenderer.iOSDarkPageBackgroundColor : ExtendedTabbedPageRenderer.iOSLightPageBackgroundColor;
cell.TextLabel.TextColor = cell.TintColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? UIColor.White : UIColor.Black;
}
}
}
}
You need override the required methods to perform the required customization via a subclass of the ShellRenderer class to customize the Tabbar appearance.
For example,in iOS, if you want to set the text of the more menu of the Shell application.
Code snippet:
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Xaminals.AppShell), typeof(Xaminals.iOS.MyShellRenderer))]
namespace Xaminals.iOS
{
public class MyShellRenderer : ShellRenderer
{
protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker()
{
return new TabBarAppearance();
}
}
public class TabBarAppearance : IShellTabBarAppearanceTracker
{
public void Dispose()
{
}
public void ResetAppearance(UITabBarController controller)
{
}
public void SetAppearance(UITabBarController controller, ShellAppearance appearance)
{
//set tabbar appearance
}
public void UpdateLayout(UITabBarController controller)
{
UITabBar tb = controller.MoreNavigationController.TabBarController.TabBar;
if (tb.Subviews.Length > 4)
{
UIView tbb = tb.Subviews[4];
UILabel label = (UILabel)tbb.Subviews[1];
label.Text = "CustomTab";
}
}
}
}
For more details, please refer to our MS official docs.
I implemented a webview using the WkWebview renderer.
Fullscreen works fine on ipone. But it doesn't work on iPad.
An image like the one below will appear.
I saw an article saying to do webViewConfiguration.allowsInlineMediaPlayback = true and applied it but it didn't work. What's the problem?
Here is my code.
public class MyWebViewRenderer : WkWebViewRenderer, IWKScriptMessageHandler, IWKNavigationDelegate
{
WKUserContentController userController;
public MyWebViewRenderer() : this(new WKWebViewConfiguration())
{
}
public MyWebViewRenderer(WKWebViewConfiguration config) : base(config)
{
userController = config.UserContentController;
var script = new WKUserScript(new NSString(_JavascriptFunction_CSharpOpenWeb), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript(script);
userController.AddScriptMessageHandler(this, "invokeAction_CSharpOpenWeb");
}
protected async override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
userController.RemoveAllUserScripts();
userController.RemoveScriptMessageHandler("invokeAction_CSharpOpenWeb");
MyWebView myWebView = e.OldElement as MyWebView;
myWebView.Cleanup();
}
if (e.NewElement != null)
{
this.NavigationDelegate = new MyNavigationDelegate(this);
var webView = (MyWebView)Element;
webView.UriChanged += async (s1, e1) =>
{
NSUrl nsurl = new NSUrl(webView.Uri);
Configuration.AllowsInlineMediaPlayback = true;
NSMutableUrlRequest request = new NSMutableUrlRequest(nsurl);
await SetCookies();
LoadRequest(request);
};
if (!string.IsNullOrEmpty(webView.Uri))
{
NSUrl nsurl = new NSUrl(webView.Uri);
NSMutableUrlRequest request = new NSMutableUrlRequest(nsurl);
await SetCookies();
LoadRequest(request);
}
}
}
}
Can you tell me what I did wrong?
Configuration.AllowsInlineMediaPlayback = true; is the OPPOSITE of what you want.
Set it to false:
Configuration.AllowsInlineMediaPlayback = false;
If that doesn't fix it, then you may need to do that earlier in the code.
See if it works when done in the constructors:
public MyWebViewRenderer() : this(new WKWebViewConfiguration())
{
EnsureFullScreen();
...
}
public MyWebViewRenderer(WKWebViewConfiguration config) : base(config)
{
EnsureFullScreen();
...
}
private void EnsureFullScreen()
{
if (Control.Configuration.AllowsInlineMediaPlayback)
Configuration.AllowsInlineMediaPlayback = false;
}
I have a WebView that contains an iframe (an HTML element). this iframe has a Peertube video plyaer, I pass &autoplay=1 to that iframe and it works good on android, iPad and browser, but on iPhone the video can not be autoplayed.
I tried custom render with WKWebView with this code:
class FullScreenEnabledWebViewRenderer : WkWebViewRenderer
{
WKUserContentController userController;
public FullScreenEnabledWebViewRenderer() : this(new WKWebViewConfiguration() { MediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypes.None, AllowsInlineMediaPlayback = true, MediaPlaybackRequiresUserAction = false, RequiresUserActionForMediaPlayback = false })
{
}
public FullScreenEnabledWebViewRenderer(WKWebViewConfiguration config) : base(config)
{
config.AllowsInlineMediaPlayback = true;
config.AllowsAirPlayForMediaPlayback = true;
config.AllowsPictureInPictureMediaPlayback = true;
config.MediaPlaybackAllowsAirPlay = true;
config.MediaPlaybackRequiresUserAction = false;
config.RequiresUserActionForMediaPlayback = false;
config.MediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypes.None;
}
}
and still does not work.
Can you help me with that please.
Webview in iOS couldn't support iframe very well . So it could better to use <video> like
var htmlSource = new HtmlWebViewSource();
htmlSource.Html = #"<html><body><video width='300' height='500' muted='muted' controls autoplay='autoplay'><source src='https://ia800201.us.archive.org/12/items/BigBuckBunny_328/BigBuckBunny_512kb.mp4' type='video/mp4'></video></body></html>";
webview.Source = htmlSource;
If it still could not auto play in iOS , we could invoked JS to play it .
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using App4;
using App4.iOS;
using WebKit;
using ObjCRuntime;
[assembly: ExportRenderer(typeof(WebView), typeof(FullScreenEnabledWebViewRenderer))]
namespace App4.iOS
{
public class FullScreenEnabledWebViewRenderer: ViewRenderer<WebView, WKWebView>
{
WKWebView wkWebView;
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var config = new WKWebViewConfiguration();
config.AllowsInlineMediaPlayback = true;
config.AllowsAirPlayForMediaPlayback = true;
config.AllowsPictureInPictureMediaPlayback = true;
config.MediaPlaybackAllowsAirPlay = true;
config.MediaPlaybackRequiresUserAction = false;
config.RequiresUserActionForMediaPlayback = false;
config.MediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypes.None;
wkWebView = new WKWebView(Frame, config);
wkWebView.NavigationDelegate = new WkWebviewPolicy();
SetNativeControl(wkWebView);
}
}
}
public class WkWebviewPolicy : WKNavigationDelegate
{
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, WKWebpagePreferences preferences, [BlockProxy(typeof(NIDActionArity2V120))] Action<WKNavigationActionPolicy, WKWebpagePreferences> decisionHandler)
{
decisionHandler.Invoke(WKNavigationActionPolicy.Allow, preferences);
//base.DecidePolicy(webView, navigationAction, preferences, decisionHandler);
}
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
base.DidFinishNavigation(webView, navigation);
var JSstr = #"var videos = document.querySelectorAll('video'); for (var i = videos.length - 1; i >= 0; i--) { var ivideo = videos[i]; ivideo.setAttribute('webkit-playsinline','\'); ivideo.play(); };";
webView.EvaluateJavaScript(JSstr,null);
}
}
}
I need to know when the user has ended the scroll on a webview displaying a agreement, to display an "Accept" only when the user has read this.
Here is my webview (xaml) :
<WebView x:Name="webView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<WebView.Source>
<HtmlWebViewSource Html="{Binding Content}" />
</WebView.Source>
How can I do that (.cs side) ?..
What is the best practice ?
Xamarin Forms - Targets : iOS & Android
Thanks for your help ;)
EDIT (iOs/Android working solution) :
Here is what I implemented to listen scroll inside the webview (JS event):
Custom WebView renderer :
namespace xxx.Views.Common.Controls
{
public class WebViewer : WebView
{
public static BindableProperty EvaluateJavascriptProperty =
BindableProperty.Create(nameof(EvaluateJavascript), typeof(Func<string, Task<string>>), typeof(WebViewer),
null, BindingMode.OneWayToSource);
public static BindableProperty RefreshProperty =
BindableProperty.Create(nameof(Refresh), typeof(Action), typeof(WebViewer), null,
BindingMode.OneWayToSource);
public static BindableProperty GoBackProperty =
BindableProperty.Create(nameof(GoBack), typeof(Action), typeof(WebViewer), null,
BindingMode.OneWayToSource);
public static BindableProperty CanGoBackFunctionProperty =
BindableProperty.Create(nameof(CanGoBackFunction), typeof(Func<bool>), typeof(WebViewer), null,
BindingMode.OneWayToSource);
public static BindableProperty GoBackNavigationProperty =
BindableProperty.Create(nameof(GoBackNavigation), typeof(Action), typeof(WebViewer), null,
BindingMode.OneWay);
public Func<string, Task<string>> EvaluateJavascript
{
get { return (Func<string, Task<string>>) this.GetValue(EvaluateJavascriptProperty); }
set { this.SetValue(EvaluateJavascriptProperty, value); }
}
public Action Refresh
{
get { return (Action) this.GetValue(RefreshProperty); }
set { this.SetValue(RefreshProperty, value); }
}
public Func<bool> CanGoBackFunction
{
get { return (Func<bool>) this.GetValue(CanGoBackFunctionProperty); }
set { this.SetValue(CanGoBackFunctionProperty, value); }
}
public Action GoBackNavigation
{
get { return (Action) this.GetValue(GoBackNavigationProperty); }
set { this.SetValue(GoBackNavigationProperty, value); }
}
}
}
Android Renderer :
[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace xxx.Droid.Renderers
{
public class WebViewRender : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
var oldWebView = e.OldElement as WebViewer;
if (oldWebView != null)
{
oldWebView.EvaluateJavascript = null;
}
var newWebView = e.NewElement as WebViewer;
if (newWebView != null)
{
newWebView.EvaluateJavascript = async js =>
{
ManualResetEvent reset = new ManualResetEvent(false);
var response = "";
Device.BeginInvokeOnMainThread(() =>
{
System.Diagnostics.Debug.WriteLine("Javascript Send: " + js);
this.Control?.EvaluateJavascript(js, new JavascriptCallback(r =>
{
response = r;
reset.Set();
}));
});
await Task.Run(() => { reset.WaitOne(); });
if (response == "null")
{
response = string.Empty;
}
return response;
};
}
if (this.Control != null && e.NewElement != null)
{
this.SetupControl();
}
}
/// <summary>
/// Sets up various settings for the Android WebView
/// </summary>
private void SetupControl()
{
// Ensure common functionality is enabled
this.Control.Settings.DomStorageEnabled = true;
this.Control.Settings.JavaScriptEnabled = true;
// Must remove minimum font size otherwise SAP PDF's go massive
this.Control.Settings.MinimumFontSize = 0;
// Because Android 4.4 and below doesn't respect ViewPort in HTML
if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
{
this.Control.Settings.UseWideViewPort = true;
}
}
}
internal class JavascriptCallback : Java.Lang.Object, IValueCallback
{
private readonly Action<string> _callback;
public JavascriptCallback(Action<string> callback)
{
this._callback = callback;
}
public void OnReceiveValue(Java.Lang.Object value)
{
System.Diagnostics.Debug.WriteLine("Javascript Return: " + Convert.ToString(value));
this._callback?.Invoke(Convert.ToString(value));
}
}
public class WebViewChromeClient : WebChromeClient
{
readonly Action<IValueCallback, Java.Lang.String, Java.Lang.String> callback;
public WebViewChromeClient(Action<IValueCallback, Java.Lang.String, Java.Lang.String> callback)
{
this.callback = callback;
}
//For Android 4.1+
[Java.Interop.Export]
public void openFileChooser(IValueCallback uploadMsg, Java.Lang.String acceptType, Java.Lang.String capture)
{
this.callback(uploadMsg, acceptType, capture);
}
// For Android 5.0+
public override bool OnShowFileChooser(WebView webView, IValueCallback filePathCallback,
FileChooserParams fileChooserParams)
{
return base.OnShowFileChooser(webView, filePathCallback, fileChooserParams);
}
}
}
iOS Renderer :
[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace xxx.iOS
{
public class WebViewRender : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
//if (this.NativeView != null && e.NewElement != null)
//{
// this.InitializeCommands((WebViewer) e.NewElement);
//}
var webView = e.NewElement as WebViewer;
if (webView != null)
{
webView.EvaluateJavascript = js => { return Task.FromResult(this.EvaluateJavascript(js)); };
}
}
private void InitializeCommands(WebViewer element)
{
var ctl = (UIWebView) this.NativeView;
ctl.ScalesPageToFit = true;
}
}
}
xaml Display :
<pages:xxxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:xxx.Views"
xmlns:controls="clr-namespace:xxx.Views.Common.Controls;assembly=xxx.View"
x:Class="xxx.Views.AboutPage">
<pages:xxxInfoContentPage.PageView>
<StackLayout HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<controls:WebViewer x:Name="webView" EvaluateJavascript="{Binding EvaluateJavascript}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<WebView.Source>
<HtmlWebViewSource Html="{Binding Content}" />
</WebView.Source>
</controls:WebViewer>
</StackLayout>
</pages:DriversInfoContentPage.PageView>
</pages:DriversInfoContentPage>
JS inside webview (redirection when the bottom is reached) :
<script type="text/javascript">
function init() {
window.onscroll = function(ev) {
if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) {
window.location.hash = "bottom";
location.reload();
}
};
}
</script>
<body onload="init()">
<!-- Scrolled content-->
</body>
Catching & Canceling Navigating Event xaml.xs side :
public AboutPage()
{
this.webView.Navigating += this.NavigatingEvent;
}
private void NavigatingEvent(object sender, WebNavigatingEventArgs e)
{
if (e.Url.Contains("bottom") || e.Url.Contains("about:blank"))
{
e.Cancel = true;
// ACTION WHEN BOTTOM IS REACHED HERE
}
}
I would recommend making your C# access the javascript.
See my tutorial:
https://www.youtube.com/watch?v=_0a7NzkNl-Q
I can change the font color like this:
var homePage = new NavigationPage(new HomePage())
{
Title = "Home",
Icon = "ionicons_2_0_1_home_outline_25.png",
BarTextColor = Color.Gray,
};
But is there a way to change the font for the Title. I would like to change it for the iOS and Android platforms only. Hoping that someone knows of custom renderer code that can help me to do this.
You need Custom Renderer , refer to this sample
iOS
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.iOS.Renderers
{
public class CustomNavigationPageRenderer : NavigationRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var att = new UITextAttributes();
UIFont customFont = UIFont.FromName("Trashtalk", 20);
UIFont systemFont = UIFont.SystemFontOfSize(20.0);
UIFont systemBoldFont = UIFont.SystemFontOfSize(20.0 , FontAttributes.Bold);
att.Font = font;
UINavigationBar.Appearance.SetTitleTextAttributes(att);
}
}
}
}
Android
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.Droid.Renderers
{
public class CustomNavigationPageRenderer : NavigationPageRenderer
{
private Android.Support.V7.Widget.Toolbar _toolbar;
public override void OnViewAdded(Android.Views.View child)
{
base.OnViewAdded(child);
if (child.GetType() == typeof(Android.Support.V7.Widget.Toolbar))
{
_toolbar = (Android.Support.V7.Widget.Toolbar)child;
_toolbar.ChildViewAdded += Toolbar_ChildViewAdded;
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if(disposing)
{
_toolbar.ChildViewAdded -= Toolbar_ChildViewAdded;
}
}
private void Toolbar_ChildViewAdded(object sender, ChildViewAddedEventArgs e)
{
var view = e.Child.GetType();
if (e.Child.GetType() == typeof(Android.Widget.TextView))
{
var textView = (Android.Widget.TextView)e.Child;
var spaceFont = Typeface.CreateFromAsset(Forms.Context.ApplicationContext.Assets, "Trashtalk.ttf");
var systemFont = Typeface.DEFAULT;
var systemBoldFont = Typeface.DEFAULT_BOLD;
textView.Typeface = spaceFont;
_toolbar.ChildViewAdded -= Toolbar_ChildViewAdded;
}
}
}
}
There is no need in a custom renderer on iOS, you can just use the Appearance API:
UINavigationBar.Appearance.SetTitleTextAttributes(new UITextAttributes
{
Font = UIFont.FromName("MyCoolFont", 20)
});
In Android you do need a renderer, however you should check against Android.Support.V7.Widget.AppCompatTextView and not Android.Widget.TextView.
Tested on Xamarin.Forms 3.4.0