I'm trying to use a library that doesn't has a .Net SDK, but as I want to use it only to return a string, I thought I could use it's JS SDK by creating a custom WebView that returns strings (https://xamarinhelp.com/xamarin-forms-webview-executing-javascript/).
The first problem that I faced was that a CustomRenderer is not called in Xamarin.Forms until the View is added to a Page (or at least I couldn't make it be called). To fix this I added a call to Platform.CreateRenderer in each platform.
It did the trick and the CustomRenderer executed. But when I tried to call a JS function to retrieve a string, the app just hung and stayed that way.
I didn't try to insert the WebView in a Page because I want it to be independent of the page that the app is current on, and as I want a "code-only" html, I don't see the point of adding it somewhere.
My classes:
JSEvaluator
namespace MyNamespace.Views
{
public class JSEvaluator : WebView
{
public static BindableProperty EvaluateJavascriptProperty = BindableProperty.Create(nameof(EvaluateJavascript), typeof(Func<string, Task<string>>), typeof(JSEvaluator), null, BindingMode.OneWayToSource);
public Func<string, Task<string>> EvaluateJavascript
{
get { return (Func<string, Task<string>>)GetValue(EvaluateJavascriptProperty); }
set { SetValue(EvaluateJavascriptProperty, value); }
}
public JSEvaluator()
{
}
}
}
UWP Renderer
[assembly: ExportRenderer(typeof(JSEvaluator), typeof(JSEvaluatorRenderer))]
namespace MyNamespace.UWP.Renderers
{
public class JSEvaluatorRenderer : WebViewRenderer
{
public JSEvaluatorRenderer() { }
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
var webView = e.NewElement as JSEvaluator;
if (webView != null)
webView.EvaluateJavascript = async (js) =>
{
return await Control.InvokeScriptAsync("eval", new[] { js });
};
}
}
}
Creation and use
if (jsEvaluator == null)
{
jsEvaluator = new JSEvaluator { Source = new HtmlWebViewSource { Html = HTML.html } };
#if __ANDROID__
Xamarin.Forms.Platform.Android.Platform.CreateRenderer(jsEvaluator);
#elif __IOS__
Xamarin.Forms.Platform.iOS.Platform.CreateRenderer(jsEvaluator);
#elif WINDOWS_UWP
Xamarin.Forms.Platform.UWP.Platform.CreateRenderer(jsEvaluator);
#endif
}
Thanks for the help :)
I had to add the WebView to a page, as #SushiHangover said in the comment. With this done, it worked as expected.
Related
Here is the code for the custom renderer i used to assign a custom icon as my back button.
namespace MyProjectName.Droid.Renderers
{
public class MyNavigationRenderer: PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
var context = (Activity)Xamarin.Forms.Forms.Context;
var toolbar = context.FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Droid.Resource.Id.toolbar);
toolbar.NavigationIcon = AndroidX.Core.Content.ContextCompat.GetDrawable(context, Resource.Drawable.bbutton_nav);
}
}
}
This code successfully replaces the native back arrow icon with my custom bbutton_nav. When i navigate forward(Navigate.PushAsync()), the custom icon appears on all the upcoming screens. But when i click on the back icon to go back one page(Navigate.PopAsync()), the old native back arrow reappears instead of the new custom icon that was set by the renderer. When i tried debugging , i found out that the renderer class was not getting called when navigating back(Navigation.PopAsync()).
Any help on how to mitigate this issue is appreciated. Thanks
Create a custom renderer for NavigationPage instead of Page , and override the OnLayout method .
Android will change the detault icon back in UpdateToolbar method , and OnLayout method is triggered every time while current page is changed.
Android Solution
[assembly: ExportRenderer(typeof(NavigationPage), typeof(MyNavigationRenderer))]
namespace FormsApp.Droid
{
public class MyNavigationRenderer : NavigationPageRenderer
{
Context _context;
AndroidX.AppCompat.Widget.Toolbar _toolbar;
public MyNavigationRenderer(Context context) : base(context)
{
_context = context;
}
public override void OnViewAdded(Android.Views.View child)
{
base.OnViewAdded(child);
if (child.GetType() == typeof(AndroidX.AppCompat.Widget.Toolbar))
{
_toolbar = (AndroidX.AppCompat.Widget.Toolbar)child;
_toolbar.SetNavigationIcon(Resource.Drawable.bbutton_nav);
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (_toolbar != null)
{
if (_toolbar.NavigationIcon != null)
{
_toolbar.NavigationIcon = AndroidX.Core.Content.ContextCompat.GetDrawable(_context, Resource.Drawable.bbutton_nav);
}
}
}
}
}
Refer to https://forums.xamarin.com/discussion/183344/how-to-change-navigation-back-button-icon .
iOS Solution
[assembly: ExportRenderer(typeof(NavigationPage), typeof(MyRenderer))]
namespace FormsApp.iOS
{
class MyRenderer : NavigationRenderer
{
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
if (this.NavigationBar.TopItem.BackBarButtonItem == null)
{
this.NavigationBar.BackIndicatorImage = UIImage.FromFile("dots.png");
this.NavigationBar.BackIndicatorTransitionMaskImage = UIImage.FromFile("dots.png");
this.NavigationBar.TopItem.BackBarButtonItem = new UIBarButtonItem("", UIBarButtonItemStyle.Plain, null);
}
}
}
}
I have a xamarin forms app that can download files to "on my iphone" folder. But when I download a file on phone, I can go there and I can share it with another app.
But I want to prevent this. When I download a file from my app, I want the file not to be uploaded to another device from "on my iphone".
How to prevent this? Probably I can prevent this with mdm, but how?
Is there a way to prevent it with mdm managed app configuration. Some of my customers said that we can prevent this with the plist file in mdm. But I have very little information about mdm. How to do it with mdm?
I need a solution for ios and android. But especially for ios.
Thank you in advance.
Thank you for reply Jack.
I looked your sending thread and I tried it. But it didn't work for me.
I created MyUINavigationItem that is inherited from UINavigationItem. And I override SetRightBarButtonItem null. After I override UINavigationItem on PdfPreviewController that is inherited from QLPreviewController. And I set NavigationItem as MyUINavigationItem object. But it doesn't work for me. My codes is like these:
public class DocumentView : IDocumentView
{
void IDocumentView.DocumentView(string file, string title)
{
PdfPreviewController previewController = new PdfPreviewController();
if (File.Exists(file))
{
previewController.NavigationItem.SetRightBarButtonItem(null, false); //I tried this line as both comment and not comment but it didn't work
previewController.NavigationItem.SetRightBarButtonItems(null, false); //I tried this line as both comment and not comment but it didn't work
previewController.DataSource = new PDFPreviewControllerDataSource(NSUrl.FromFilename(file), title);
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(previewController, true, null);
}
}
}
public class PdfPreviewController : QLPreviewController
{
MyUINavigationItem item = new MyUINavigationItem();
public override UINavigationItem NavigationItem => item;
}
public class MyUINavigationItem : UINavigationItem
{
public MyUINavigationItem()
{
}
public override void SetRightBarButtonItem(UIBarButtonItem item, bool animated)
{
//base.SetRightBarButtonItem(item, animated); //I tried this line as comment but it didn't work or
//base.SetRightBarButtonItem(null, animated); //I tried this line but it didn't work
}
public override void SetRightBarButtonItems(UIBarButtonItem[] items, bool animated)
{
//base.SetRightBarButtonItems(items, animated); //I tried this line as comment but it didn't work or
//base.SetRightBarButtonItems(null, animated); //I tried this line but it throwed exception
//base.SetRightBarButtonItems(new UIBarButtonItem[0], animated); //I tried this line but it didn't work
}
}
But after your suggestion I looked for enable or hide share button. And I could access child view controller of UINavigationController and I set RightBarButtonItem enable value false.
public class PdfPreviewController : QLPreviewController
{
public override void ViewDidAppear(bool animated)
{
try
{
ActionMenuControl();
}
catch (Exception ex)
{
}
}
public override void ViewDidLayoutSubviews()
{
try
{
ActionMenuControl();
}
catch (Exception ex)
{
}
}
public void ActionMenuControl()
{
try
{
if (this.ChildViewControllers.Length != 0)
{
var navigationController = this.ChildViewControllers.First() as UINavigationController;
if (navigationController.View.Subviews.Length != 0)
{
var layoutContainerView = navigationController.View.Subviews.FirstOrDefault(x => x is UINavigationBar) as UINavigationBar;
if (layoutContainerView != null)
{
if (layoutContainerView.Items.Length != 0)
{
var item = layoutContainerView.Items[0];
if (item.RightBarButtonItems.Length != 0)
item.RightBarButtonItem.Enabled = false;
}
}
}
var toolbar = navigationController.Toolbar;
if (toolbar != null)
{
if(toolbar.Items.Length != 0)
{
toolbar.Items[0].Enabled = false;
}
}
}
}
catch (Exception ex)
{
}
}
}
It works for right bar. But if opens mp3 files, share button appear on toolbar (on bottom) And I didn't access it. How can I do it for mp3 files? How to access toolbar? (Actually it doesn't work when opens and not move it. But if I open it and move it, it works (because it enters ViewDidLayoutSubviews events))
Sorry for long reply. But I wanted to tell about what I did. Because maybe I did missed something.
I’m new to Xamarin.Forms and tried using WebView on my Windows 10 x64 v1803 machine with UWP but I can’t see how to get it to work with WebGL.
Sites which use WebGL either display a message that “Your video card does not support WebGL or just don’t display and graphical content at all.
Is this a limitation of UWP or WebView itself?
Is it a WebView configuration issue?
WebGL works in all other browsers on this machine.
UWP WebView control is support WebGL. There is similar issue case in msdn you could refer. Please try to use SeparateProcess mode WebView to replace the default one.
public MainPage()
{
this.InitializeComponent();
var MyWebView = new WebView(WebViewExecutionMode.SeparateProcess);
MyWebView.Source = new Uri("http://cycleblob.com/");
this.RootGrid.Children.Add(MyWebView);
}
I had the same problem, but with the newer Xamarin Forms it took a little more poking around to get this took work right. However, I do like that they moved the native WebView resolver back to the responsibility of the UWP/iOS/Android project (as a native XAML object) instead of using code branching with compiler directives in the Shared project.
Start by creating a HybridWebView class in the shared project to use as your WebForm view object:
public class HybridWebView : Xamarin.Forms.WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
action = callback;
}
public void Cleanup()
{
action = null;
}
public void InvokeAction(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
}
Then in the UWP project, create a custom renderer, which will construct the native WebView and relay the events back to the WebForms object in the Shared project:
Put this at the top of the namespace, to link the HybridWebView with the Custom Renderer:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(WebViewRenderer2))]
Then create the renderer class (for the IOS and android projects, if you leave this class out, it defaults to the standard native controls which seem to work fine for me):
public class WebViewRenderer2 : ViewRenderer<Xamarin.Forms.WebView, Windows.UI.Xaml.Controls.WebView>, IWebViewDelegate
{
Windows.UI.Xaml.Controls.WebView _control;
public void LoadHtml(string html, string baseUrl)
{
}
public void LoadUrl(string url)
{
}
protected override void Dispose(bool disposing)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
if (_control == null) {
_control = new Windows.UI.Xaml.Controls.WebView(WebViewExecutionMode.SeparateProcess);
SetNativeControl(_control);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var xamWebView = sender as HybridWebView;
switch(e.PropertyName.ToLower())
{
case "source":
var urlSource = xamWebView.Source as Xamarin.Forms.UrlWebViewSource;
_control.Source = new Uri(urlSource.Url);
break;
case "width":
_control.Width = xamWebView.Width;
break;
case "height":
_control.Height = xamWebView.Height;
break;
case "isfocused":
var focused = xamWebView.IsFocused;
if (focused)
_control.Focus(FocusState.Programmatic);
else
_control.Focus(FocusState.Unfocused);
break;
}
}
}
You can also use the Custom Renderer to inject scripts, and you can use it to communicate from the native webview back to the Xamarin App, as seen here: HybridWebView Communication
I'm just creating a simple example where I want to enable/disable a Switchcell (named switch2) by turning on/off another switchcell (named switch1).
I'm using the binding method. I've tried this code with an "Entry" element (trying to disable it) and it works perfectly, but with the property "IsEnabledProperty" of "switchcell" it seems not working. (I'm not using Xaml, i'm using PCL).
Xamarin Forms updated to the latest version (2.3.0.49).
Is this a Xamarin issue?
Here's the code:
BindingContext = new DetailsViewModel();
SwitchCell switch1 = new SwitchCell()
{
Text = "Switch",
};
switch1.SetBinding(SwitchCell.OnProperty, new Binding("Test", BindingMode.TwoWay));
SwitchCell switch2 = new SwitchCell()
{
Text = "Visibilita",
};
switch2.SetBinding(SwitchCell.IsEnabledProperty, "Test");
and here is the DetailsViewModel.cs:
public class DetailsViewModel : INotifyPropertyChanged
{
bool test;
public bool Test
{
get { return test; }
set
{
if (test != value)
{
test = value;
OnPropertyChanged("Test");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Actually this is bug from Xamarin, I think is not yet fixed first check without binding whether is working or not
I'm working on a Xamarin.forms project but i need to use Android.Widget.AutoCompleteTextView how can i apply that?
When i am trying to add AutoCompleteTextView UserNameAutoComplete; to ContentPage i get the following error:
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
Padding = new Thickness(25),
Children =
{
UserNameAutoComplete,
passwordEditor,
}
};
cannot convert from 'Android.Widget.AutoCompleteTextView' to
'Xamarin.Forms.View'
Android.Widget.AutoCompleteTextView is a View from Android.
Solution for PCL:
You can't use platform specific View's on Xamarin Forms (PCL) ContentPage.
To use platform specific View you should use a custom render.
There is a blog post from #JamesMontemagno that shows how to do what you need.
This code is draft exemple please use it as such.
1 - Create your own custom Xamarin.Forms control that will be renderered in Android as an AutoCompleteTextView:
public class AutoCompleteView : View
{
// Place need properties here.
}
2 - In Android project add a renderer for AutoCompleteView:
[assembly: ExportRenderer(typeof(AutoCompleteView), typeof(AutoCompleteViewRenderer))]
namespace App.Droid
{
public class AutoCompleteViewRenderer : ViewRenderer<AutoCompleteView, AutoCompleteTextView>
{
// Initialize the AutoCompleteTextView
protected override void OnElementChanged (ElementChangedEventArgs<AutoComplete> e)
{
base.OnElementChanged (e);
if (e.OldElement != null || this.Element == null)
return;
var autoComplete = new AutoCompleteTextView(Forms.Context);
SetNativeControl (autoComplete);
}
// Use the control here.
protected override void OnElementPropertyChanged (object sender, PropertyChangedEventArgs e) {
base.OnElementPropertyChanged (sender, e);
if (this.Element == null || this.Control == null)
return;
// variable this.Control is the AutoCompleteTextView, so you an manipulate it.
}
}
}
Solution for Shared Project:
When using Shared Project there is a possibility to use Native Embedding, like:
...
var textView = new TextView (Forms.Context) { Text = originalText };
stackLayout.Children.Add (textView);
contentView.Content = textView.ToView();
...