How custom renderer? - c#

I am working with Xamarin WebView and wanted to make my Xamarin app that will still load the Website when its offline.
Is there any Idea about Xamarin Webview load website when Offline?
I have been searching for this, I only see Java Android WebViews and nothing to see Xamarin forms

We could use Custom Renderer to set the CacheMode on iOS and Android .
in Forms
Create a 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); }
}
}
in iOS
There is a ReturnCacheDataElseLoad Cache type of NSUrlRequestCachePolicy . You could set the value as NSURLRequestReturnCacheDataElseLoad .
Use existing cache data, regardless or age or expiration date, loading from originating source only if there is no cached data.
using System.ComponentModel;
using xxx;
using xxx.iOS;
using Foundation;
using UIKit;
using WebKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly:ExportRenderer(typeof(MyWebView),typeof(MyWebViewRenderer))]
namespace xxx.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);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == "Url")
{
NSUrlRequest request = new NSUrlRequest(new NSUrl(Element.Url), NSUrlRequestCachePolicy.ReturnCacheDataElseLoad, 5);
Control.LoadRequest(request);
}
}
}
}
in Android
using Android.Content;
using xxx;
using xxx.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(MyWebView), typeof(MyWebViewRenderer))]
namespace xxx.Droid
{
public class MyWebViewRenderer : WebViewRenderer
{
public MyWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
Control.Settings.CacheMode = Android.Webkit.CacheModes.CacheElseNetwork;
Control.Settings.JavaScriptEnabled = true;
}
}
}
}
Now you just need to define it in xaml like following
<local:MyWebView Url="xxx" />

I added Control.LoadUrl(((MyWebView)Element).Url); line in Android custom renderer otherwise url isn't displayed. Also OnElementPropertyChanged if(e.property==url) is never true I moved this block to OnElementChanged method in iOS renderer.

Related

Opening a pdf in webview needs more than 5 clicks or no load

I'm trying to open pdf with a webview but some pdf says "No Preview Avaliable", or you have to give it many clicks to open it or directly some crash the app.
Im using the web https://www.pdfpdf.com/samples.html and the first 5 pdfs work fine although sometimes I have to click them 5 times to open them. With those of the last sections, the app crashes when I click on one of them.
The following code can work if I open the pdf in WebView directly .
This is my code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="webviewproblema.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<StackLayout>
<WebView
x:Name="Browser"
HeightRequest="1000"
Navigating="Browser_Navigating"
Source="{Binding HTMLContent}"
WidthRequest="1000" />
</StackLayout>
</ContentPage>
public partial class MainPage : ContentPage
{
public string HTMLContent { get; set; }
public MainPage()
{
InitializeComponent();
Browser.Source = "https://www.pdfpdf.com/samples.html";
}
private void Browser_Navigating(object sender, WebNavigatingEventArgs e)
{
// With this,the app crash
//string url = "https://docs.google.com/gview?embedded=true&url=" + System.Net.WebUtility.UrlEncode(e.Url);
Browser.IsVisible = true;
string url = e.Url;
if ( e.Url.EndsWith(".pdf") || e.Url.EndsWith(".PDF") && !e.Url.Contains("drive.google.com"))
{
Browser.Source = "https://drive.google.com/viewerng/viewer?embedded=true&url=" + url;
}
}
}
You could use custom renderer to open external link in a website .
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(((MyWebView)Element).Uri);
web_view.SetWebViewClient(new MyWebViewClient());
SetNativeControl(web_view);
Control.Settings.JavaScriptEnabled = true;
}
}
}
public class MyWebViewClient : WebViewClient
{
public override void OnReceivedSslError(Android.Webkit.WebView view, SslErrorHandler handler, SslError error)
{
handler.Proceed();
}
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
{
var url = request.Url.ToString();
if (request.Url.ToString().EndsWith(".pdf") || request.Url.ToString().EndsWith(".PDF") && !request.Url.ToString().Contains("drive.google.com"))
{
view.LoadUrl("https://drive.google.com/viewerng/viewer?embedded=true&url=" + request.Url.ToString());
view.SetWebViewClient(new MyWebViewClient());
}
return 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);
_wkWebView.NavigationDelegate = new MyDelegete();
SetNativeControl(_wkWebView);
}
if(e.NewElement!=null)
{
var webview = Element as MyWebView;
var url = webview.Uri;
if (url.EndsWith(".pdf") || url.EndsWith(".PDF") && !url.Contains("drive.google.com"))
{
Control.LoadRequest(new NSUrlRequest(new NSUrl("https://drive.google.com/viewerng/viewer?embedded=true&url=" + webview.Uri)));
}
else
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(webview.Uri)));
}
}
}
}
public class MyDelegete :WKNavigationDelegate
{
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, WKWebpagePreferences preferences, Action<WKNavigationActionPolicy, WKWebpagePreferences> decisionHandler)
{
// base.DecidePolicy(webView, navigationAction, preferences, decisionHandler);
var url = navigationAction.Request.Url.ToString();
if (url.EndsWith(".pdf") || url.EndsWith(".PDF") && !url.Contains("drive.google.com"))
{
webView.LoadRequest(new NSUrlRequest(new NSUrl("https://drive.google.com/viewerng/viewer?embedded=true&url=" + url)));
}
decisionHandler.Invoke(WKNavigationActionPolicy.Allow, preferences);
}
}
}
in xaml
Now you can use it in xaml like
<StackLayout>
<local:MyWebView x:Name="Browser" Uri="https://xxx" HeightRequest="1000" WidthRequest="1000" />
</StackLayout>

How to create standard design of ios switch on android?

I want to create a custom switch on android which looks like standard ios switch.
Please help me do it
We could implement it by using Custom Renderer
in Forms
Create a Custom Button
public class CustomSwitch : Button
{
public bool IsToggle { get; set; }
public event EventHandler Toggled;
public void OnToggled() =>
Toggled?.Invoke(this, null);
}
in Android Project
Firstly, we need install the package Xamarin.Android.SwitchButton from Nuget .
And in the ButtonRenderer
using Android.Content;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using App14.Droid;
using Com.Kyleduo.Switchbutton;
using App14;
[assembly:ExportRenderer(typeof(CustomSwitch),typeof(MySwitchRenderer))]
namespace App14.Droid
{
public class MySwitchRenderer : ButtonRenderer
{
Context context { get;}
public MySwitchRenderer(Context context) : base(context)
{
this.context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
SwitchButton switchButton = new SwitchButton(context);
// switchButton.SetHighlightColor(Android.Graphics.Color.Green);
switchButton.CheckedChange += SwitchButton_CheckedChange;
SetNativeControl(switchButton);
}
}
private void SwitchButton_CheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
{
var customSwitch = Element as CustomSwitch;
customSwitch.IsToggle = e.IsChecked;
customSwitch.OnToggled();
}
}
}
Now in Forms we need to use Device class to add different Element on iOS and Android .
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<OnPlatform x:TypeArguments="View">
<On Platform="Android">
<local:CustomSwitch Toggled="CustomSwitch_Toggled" />
</On>
<On Platform="iOS">
<Switch Toggled="Switch_Toggled" />
</On>
</OnPlatform>
</StackLayout>
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void Switch_Toggled(object sender, ToggledEventArgs e)
{
Switch _switch = sender as Switch;
ToggledChanged(_switch.IsToggled);
}
private void CustomSwitch_Toggled(object sender, EventArgs e)
{
CustomSwitch customSwitch = sender as CustomSwitch;
ToggledChanged(customSwitch.IsToggle);
}
void ToggledChanged(bool isToggle)
{
DisplayAlert("Title", $"IsToggled{isToggle}", "OK");
}
}
You need to create custom renderer for switch;
public class CustomSwitchRenderer : SwitchRenderer
{
public CustomSwitchRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Switch> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || e.NewElement == null)
return;
/*
Control.TrackDrawable.MinimumWidth holds the value of the tracker size.
to change it, you need a new shape for tracker.
*/
Control.SetTrackResource(Resource.Drawable.tracker);
}
}
So you need to create a drawable for tracker under your android project.

Xamarian.Form default MasterDetail Project Application.Current.MainPage is null

I'm really trying to work my way through a Xamarian.Form app an I'm finding it difficult because the template out of the box doesn't seem to work.
Basically on the menu I'm trying to nav to the Browse or About page.
however MainPage RootPage { get => Application.Current.MainPage as MainPage; } is returning null.
I've made no changes to this template but I do understand C# so I roughly understand the issue but I'm not aware as to why this is returning null, certainly from the starting template.
MenuPage.xaml.xs
using ProspectGator.Models;
using System;
using System.Collections.Generic;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ProspectGator.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MenuPage : ContentPage
{
MainPage RootPage { get => Application.Current.MainPage as MainPage; }
List<HomeMenuItem> menuItems;
public MenuPage()
{
InitializeComponent();
menuItems = new List<HomeMenuItem>
{
new HomeMenuItem {Id = MenuItemType.Browse, Title="Browse" },
new HomeMenuItem {Id = MenuItemType.About, Title="About" }
};
ListViewMenu.ItemsSource = menuItems;
ListViewMenu.SelectedItem = menuItems[0];
ListViewMenu.ItemSelected += async (sender, e) =>
{
if (e.SelectedItem == null)
return;
var id = (int)((HomeMenuItem)e.SelectedItem).Id;
await RootPage.NavigateFromMenu(id);
};
}
}
}
App.xaml.cs
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using ProspectGator.Views;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace ProspectGator
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new NavigationPage( new MainPage());
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
Typically I would expect this property to be set or already set but I'm not sure. I do have a MainPage.xaml.

When using Prism to DeepLink to a row inside a ListView, how can I update the ListView scroll and highlight?

I am using Xamarin Forms with Prism, based on this GitHub sample..
Desired Behavior
Deep link is clicked, showing the detail view:
User presses back button. Scroll and highlight the linked selection (not happening).
None of the OnNavigation events are firing. Is this a bug? How do I accomplish this?
App.Xaml
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override async void OnInitialized()
{
InitializeComponent();
await NavigationService.NavigateAsync("MainTabbedPage/NavigationPage/ShowsListPage/DetailPage?show=279121");
//await NavigationService.NavigateAsync("MainTabbedPage/NavigationPage/ShowsListPage");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<UpcomingShowsPage>();
Container.RegisterTypeForNavigation<ShowsListPage>(); // <-- Problematic ListView
Container.RegisterTypeForNavigation<DetailPage>();
Container.RegisterTypeForNavigation<MainTabbedPage>();
Container.RegisterTypeForNavigation<NavigationPage>();
Container.RegisterType<ITsApiService, TsApiService>();
}
ShowsListPage.xaml
ContentPage is using the Prism directive: prism:ViewModelLocator.AutowireViewModel="True". (nothing special)
ShowsListPageViewModel.cs
using System.Collections.ObjectModel;
using InfoSeries.Core.Models;
using InfoSeries.Core.Services;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using Xamarin.Forms;
namespace DeepNavigation.ViewModels
{
public class ShowsListPageViewModel : BindableBase, INavigationAware
{
private readonly ITsApiService _tsApiService;
private readonly INavigationService _navigationService;
private ObservableCollection<SerieFollowersVM> _highlightSeries;
public ObservableCollection<SerieFollowersVM> HighlightSeries
{
get { return _highlightSeries; }
set { SetProperty(ref _highlightSeries, value); }
}
public ShowsListPageViewModel(ITsApiService tsApiService, INavigationService navigationService)
{
_tsApiService = tsApiService;
_navigationService = navigationService;
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public async void OnNavigatedTo(NavigationParameters parameters)
{
var series = await _tsApiService.GetStatsTopSeries();
HighlightSeries = new ObservableCollection<SerieFollowersVM>(series);
}
public void OnNavigatingTo(NavigationParameters parameters)
{
}
private DelegateCommand<ItemTappedEventArgs> _goToDetailPage;
public DelegateCommand<ItemTappedEventArgs> GoToDetailPage
{
get
{
if (_goToDetailPage == null)
{
_goToDetailPage = new DelegateCommand<ItemTappedEventArgs>(async selected =>
{
NavigationParameters param = new NavigationParameters();
var serie = selected.Item as SerieFollowersVM;
param.Add("show", serie.Id);
await _navigationService.NavigateAsync("DetailPage", param);
});
}
return _goToDetailPage;
}
}
}
}
Question
How can I get the back button to select the list view?
Is there any platform guidance saying that the back button after a deep link must go to the source calling application.. rendering this question useless? (e.g. pop the navigation back to Chrome/Safari)

Xamarin Forms webview not displaying on device

Ive developing a webview app in Xamarin.Forms over the last few days and ive been testing it on an android and iOS emulator and it seems to work just fine in the emulators but when i went and tried to test it on my own Android device, it just showed the xamarin splashscreen(im using the trial version at the moment) and then just transitioned to a blank white screen instead of the webview.
Does anyone have any ideas why it is doing this?
I will attach my code below:
App.cs
using Xamarin.Forms;
namespace WebViewApp
{
public class App() : Application
{
public App()
{
// The root page of your application
MainPage = new WebPage();
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
WebPage.cs
using Xamarin.Forms;
namespace WebViewApp
{
public class WebPage : ContentPage
{
private const string URL = "https://www.google.com";
private const int PADDING_WIDTH = 0;
private int paddingHeight;
private WebView webView;
public WebPage()
{
webView = new WebView
{
Source = URL,
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand
};
CheckDevice();
Content = new StackLayout
{
Padding = new Thickness(PADDING_WIDTH, GetPaddingHeight()),
Chrildren = { webView }
};
}
public int GetPaddingHeight()
{
return paddingHeight;
}
/// <summary>
/// This will set the padding height for the webview when displayed
/// <summary>
/// <param name="pHeight">Set integer value for the padding height.</param>
public void SetPaddingHeight(int pHeight)
{
paddingHeight = pHeight;
}
private void CheckDevice()
{
if(Device.OS == TargetPlatform.Android)
{
SetPaddingHeight(0);
}
else if(Device.OS == TargetPlatform.iOS)
{
SetPaddingHeight(20);
}
}
}
}
** UPDATE **
I am using a company website but I have been testing this app out with a number of different sites such as google, youtube, and amazon. It would seem that the only site that wont display on my device is my companies website(its a responsive website) but all of the others do.
Since version 9, iOS will only allow your application to communicate with servers that implement best-practice security by default. Values must be set in Info.plist to enable communication with insecure servers.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads </key>
<true/>
</dict>
You must set the two properties HeightRequest and WidthRequest.
WebView requires that HeightRequest and WidthRequest are specified
when contained in StackLayout or RelativeLayout. If you fail to
specify those properties, the WebView will not render.
Source: https://developer.xamarin.com/guides/xamarin-forms/user-interface/webview/
Here is my code to display a webview in a Xamarin.Forms App.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin;
using Xamarin.Forms;
namespace mynamespace
{
public class WebsitePage : ContentPage
{
public WebsitePage(string url)
{
Label lbl_header = new Label
{
Text = "WebView",
HorizontalOptions = LayoutOptions.Center
};
WebView webview = new WebView
{
Source = url,
VerticalOptions = LayoutOptions.FillAndExpand
};
this.Content = new StackLayout
{
Children = {
webview
}
};
}
}
}

Categories