I was using HybridWebviewRenderer for loading a url on WebView for my application using Xamarin forms. Currently when I click a button in web view, it shows a alert popup with one closeButton. when I click on the closeButton , I'm currently navigating to a page and the navigation is working properly, but when I come back to the same screen, the alert Is not dismissed.? The alert is all done on the web view side , not code is done for creating alert in Xamarin forms. how can we dismiss the alert in web view for Xamarin forms??
I have checked your code, you need to put the if (Control == null) inside the if (e.NewElement != null).You can check the code snippet below for your reference:
using System;
using System.ComponentModel;
using Android.Content;
using Android.Service.Controls;
using Android.Webkit;
using HybridWebView.Droid.Renderer;
using HybridWebView.Renderer;
using Java.Interop;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CustomWebView), typeof(HybridWebViewRenderer))]
namespace HybridWebView.Droid.Renderer
{
public class HybridWebViewRenderer : ViewRenderer<CustomWebView, Android.Webkit.WebView>
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
Context _context;
public string BaseUrl = "file:///android_asset/";
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == "Source")
{
Control.LoadDataWithBaseURL(BaseUrl, Element.Source, "text/html", "UTF-8", null);
}
}
protected override void OnElementChanged(ElementChangedEventArgs<CustomWebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as CustomWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
if (Control == null)
{
var webView = new Android.Webkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
SetNativeControl(webView);
}
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadDataWithBaseURL(BaseUrl, Element.Source, "text/html", "UTF-8", null);
}
}
}
Related
My goal is to add an OK button on iOS numeric keyboard; I can achieve that very easily with a custom renderer :
public class ExtendedEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Element == null)
{
return;
}
// Check only for Numeric keyboard
if (this.Element.Keyboard == Keyboard.Numeric)
{
this.AddDoneButton();
}
}
/// <summary>
/// <para>Add toolbar with Done button</para>
/// </summary>
protected void AddDoneButton()
{
var toolbar = new UIToolbar(new RectangleF(0.0f, 0.0f, 50.0f, 44.0f));
var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, delegate
{
this.Control.ResignFirstResponder();
var baseEntry = this.Element.GetType();
((IEntryController)Element).SendCompleted();
});
toolbar.Items = new UIBarButtonItem[] {
new UIBarButtonItem (UIBarButtonSystemItem.FlexibleSpace),
doneButton
};
this.Control.InputAccessoryView = toolbar;
}
}
but my question is how can we add this keyboard behavior on a Xamarin prompt dialog.
await DisplayPromptAsync("Title", "Content", keyboard: Keyboard.Numeric);
If you want to customize the Keyboard of AlertView in iOS , you could implement it by using DependencyService
in Forms
create a Interface
public interface IDisplayPrompt
{
void DisplayPrompt(string Title,string Content,Keyboard keyboard,Action<string> SubmitAction,Action CancelAction);
}
in iOS
using System;
using app55;
using app55.iOS;
using Xamarin.Forms;
using UIKit;
using System.Drawing;
[assembly: Dependency(typeof(DisplayPromptImplement))]
namespace app55.iOS
{
public class DisplayPromptImplement:IDisplayPrompt
{
public DisplayPromptImplement()
{
}
public void DisplayPrompt(string Title, string Content, Keyboard keyboard, Action<string> SubmitAction, Action CancelAction)
{
UIAlertController alertController = UIAlertController.Create(Title,Content,UIAlertControllerStyle.Alert);
UIAlertAction OKAction = UIAlertAction.Create("OK",UIAlertActionStyle.Default,(action)=> {
//click OK Button
var content = alertController.TextFields[0].Text;
SubmitAction.Invoke(content);
});
UIAlertAction DismissAction = UIAlertAction.Create("Cancel", UIAlertActionStyle.Cancel, (action) => {
//click Cancel Button
CancelAction.Invoke();
});
alertController.AddTextField((field)=> {
if (keyboard == Keyboard.Numeric)
field.KeyboardType = UIKeyboardType.NumberPad;
AddDoneButton(field);
});
alertController.AddAction(OKAction);
alertController.AddAction(DismissAction);
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alertController,true,null);
}
protected void AddDoneButton(UITextField field)
{
var toolbar = new UIToolbar(new RectangleF(0.0f, 0.0f, 50.0f, 44.0f));
var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, delegate
{
field.ResignFirstResponder();
});
toolbar.Items = new UIBarButtonItem[] {
new UIBarButtonItem (UIBarButtonSystemItem.FlexibleSpace),
doneButton
};
field.InputAccessoryView = toolbar;
}
}
}
Now in Forms we could invoked it like following
void Button_Clicked(System.Object sender, System.EventArgs e)
{
if(Device.RuntimePlatform=="iOS")
{
DependencyService.Get<IDisplayPrompt>().DisplayPrompt("Title", "Please Input Message", Keyboard.Numeric, (content) =>
{
/// get the content that you input
label.Text = content.ToString();
}, null);
}
else
{
// other platform
//...await DisplayPromptAsync
}
}
Screen Shot
I am having some difficulty figuring out how to properly update my existing custom renderer I have for my DatePicker on iOS to display the DatePicker with a different preferred style as is mentioned in this article here (albeit it is for swift) https://medium.com/better-programming/introducing-swifts-new-modern-date-picker-37bb5e0a106
My renderer is as follows:
public class BorderlessDatePickerRenderer : DatePickerRenderer
{
public override void LayoutSubviews()
{
base.LayoutSubviews();
var element = Element as BorderlessDatePicker;
Control.BorderStyle = UITextBorderStyle.None;
if (element.Date.Year == 1900) {
Control.Text = "";
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var element = Element as BorderlessDatePicker;
if (Control != null && element.Date.Year == 1900) {
Control.Text = "";
}
}
The BorderlessDatePicker itself is just an empty class that extends the DatePicker Xamarin.Forms control. The root of my woes is that I am not sure how to properly set a PreferredDatePickerStyle on my Control object given that Control is a UITextField under the hood instead of a UIDatePicker. In essence what I would like to do is instead of displaying the Date picker using the compact style that seems to be default for iOS 14, I would like for it to be displayed as wheels instead by being to do something like:
PreferredDatePickerStyle = UIDatePickerStyle.Wheels;
After some more researching, and browsing the xamarin github, I've come across this solution:
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
base.OnElementChanged(e);
if(e.NewElement != null && this.Control != null)
{
try
{
if (UIDevice.CurrentDevice.CheckSystemVersion(14, 0))
{
UIDatePicker picker = (UIDatePicker)Control.InputView;
picker.PreferredDatePickerStyle = UIDatePickerStyle.Wheels;
}
} catch (Exception ex)
{
Log.Error(ex, "Failed to set PreferredDatePickerStyle to be UIDatePickerStyle.Wheels for iOS 14.0+");
}
}
}
I have some pages in Xamarin.Forms,since I need to build a custom camera,so I used Xamarin.Android.
now I need to go to Xamarin.Android/iOS from page of Xamarin.Forms and and back to Xamarin.Forms again. how can I do it?
pls help
You don't necessarily need to jump between Xamarin.Forms Views/Pages and Project Specific (iOS/Android) Views manually you can do it by using CustomRenderes which are part of Xamarin.Forms.
Basically you would create a Custom Page on Xamarin.Forms then specifying in each platform project how you want that page to look. You would attach this CustomRender to your Custom Page (the one you made in Xamarin.Forms) and the Framework would do the rest for you.
You can read about Custom Renderers here. And you can see a very similar example of what you are looking right in the Xamarin documentation here.
Hope this helps.-
Welcome to SO !
Baesd on a Xamarin Forms project , you can use Custom Renderer to achieve that .
Such as Creating the Custom Control :
public class CameraPreview : View
{
public static readonly BindableProperty CameraProperty = BindableProperty.Create (
propertyName: "Camera",
returnType: typeof(CameraOptions),
declaringType: typeof(CameraPreview),
defaultValue: CameraOptions.Rear);
public CameraOptions Camera {
get { return (CameraOptions)GetValue (CameraProperty); }
set { SetValue (CameraProperty, value); }
}
}
In Xaml :
<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
...>
<ContentPage.Content>
<StackLayout>
<Label Text="Camera Preview:" />
<local:CameraPreview Camera="Rear"
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
Now you can create the Custom Renderer on each Platform.
Android :
[assembly: ExportRenderer(typeof(CustomRenderer.CameraPreview), typeof(CameraPreviewRenderer))]
namespace CustomRenderer.Droid
{
public class CameraPreviewRenderer : ViewRenderer<CustomRenderer.CameraPreview, CustomRenderer.Droid.CameraPreview>
{
CameraPreview cameraPreview;
public CameraPreviewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<CustomRenderer.CameraPreview> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
cameraPreview.Click -= OnCameraPreviewClicked;
}
if (e.NewElement != null)
{
if (Control == null)
{
cameraPreview = new CameraPreview(Context);
SetNativeControl(cameraPreview);
}
Control.Preview = Camera.Open((int)e.NewElement.Camera);
// Subscribe
cameraPreview.Click += OnCameraPreviewClicked;
}
}
void OnCameraPreviewClicked(object sender, EventArgs e)
{
if (cameraPreview.IsPreviewing)
{
cameraPreview.Preview.StopPreview();
cameraPreview.IsPreviewing = false;
}
else
{
cameraPreview.Preview.StartPreview();
cameraPreview.IsPreviewing = true;
}
}
...
}
}
iOS :
[assembly: ExportRenderer (typeof(CameraPreview), typeof(CameraPreviewRenderer))]
namespace CustomRenderer.iOS
{
public class CameraPreviewRenderer : ViewRenderer<CameraPreview, UICameraPreview>
{
UICameraPreview uiCameraPreview;
protected override void OnElementChanged (ElementChangedEventArgs<CameraPreview> e)
{
base.OnElementChanged (e);
if (e.OldElement != null) {
// Unsubscribe
uiCameraPreview.Tapped -= OnCameraPreviewTapped;
}
if (e.NewElement != null) {
if (Control == null) {
uiCameraPreview = new UICameraPreview (e.NewElement.Camera);
SetNativeControl (uiCameraPreview);
}
// Subscribe
uiCameraPreview.Tapped += OnCameraPreviewTapped;
}
}
void OnCameraPreviewTapped (object sender, EventArgs e)
{
if (uiCameraPreview.IsPreviewing) {
uiCameraPreview.CaptureSession.StopRunning ();
uiCameraPreview.IsPreviewing = false;
} else {
uiCameraPreview.CaptureSession.StartRunning ();
uiCameraPreview.IsPreviewing = true;
}
}
...
}
}
More info can refer to this official sample .
I would like to extend all the contentpages in my xamarin.forms app with a native view in UWP. I can basically go to each and every page and embed a native view but i dont want this. I want to know if there is a way to do it using a pagerenderer. I tried doing like below.
my idea was to get current page rendering and extend the content with native view and stacklayout and define app.content again with this change. It works in general. If you run the small test project below, you can see that native UWP FontIcons are displayed for each page but there is a problem, if i navigate same page 2 times in MasterDetail in the attached project, page becomes blank. Why is this happening?
and is the approach below best for my case? I am open for alternative solutions.
[assembly: ExportRenderer(typeof(ContentPage), typeof(App3.UWP.ContentPageRenderer))]
namespace App3.UWP
{
public class ContentPageRenderer : PageRenderer
{
bool isDisposing = false;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
if (isDisposing)
return;
if (e.OldElement != null || Element == null)
{
return;
}
ContentPage page = ((ContentPage)Element);
if (page.Content == null)
return;
var XboxControls = new MyUserControl1();
StackLayout stackLayout = new StackLayout() { Orientation = StackOrientation.Vertical };
stackLayout.Children.Add(page.Content);
stackLayout.Children.Add(XboxControls.ToView());
page.Content = stackLayout;
}
protected override Windows.Foundation.Size ArrangeOverride(Windows.Foundation.Size finalSize)
{
return base.ArrangeOverride(finalSize);
}
protected override void Dispose(bool disposing)
{
isDisposing = disposing;
base.Dispose(disposing);
}
}
Test Project
I'm trying to insert a UWP specific child in the custom renderer of a StackLayout.
However, in the sample code below, Control is always null whereas my StackLayout has Children. Maybe StackPanel is not what StackLayout is rendered into in UWP.
public class MyRenderer : ViewRenderer<StackLayout, StackPanel>
{
private bool _childAdded;
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (!_childAdded && Control?.Children != null)
{
_childAdded = true;
Control.Children.Insert(0, new Windows.UI.Xaml.Shapes.Rectangle());
}
base.OnElementPropertyChanged(sender, e);
}
}
Some modification in you are cade because you are calling base.OnElementPropertyChanged(sender,e) after code implementation. Just try to use below code.
public class MyRenderer : ViewRenderer<StackLayout, StackPanel>
{
private bool _childAdded;
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if(Control==null)
return;
if (!_childAdded && Control.Children != null)
{
_childAdded = true;
Control.Children.Insert(0, new Windows.UI.Xaml.Shapes.Rectangle());
}
}
}
The StackLayout (Layout) renderer is ViewRenderer and implemented on UWP by FrameworkElement; Renderer Base Classes and Native Controls.
Theoretical renderer:
public class MyRenderer : ViewRenderer<StackLayout, FrameworkElement>
...
Control is always null whereas my StackLayout has Children. Maybe StackPanel.
Derive from official document,
In Xamarin.Forms, all layout classes derive from the Layout<T> class and constrain the generic type to View and its derived types. But the layout of children element is incorrect.
And the match Native control within UWP platform is LayoutRenderer. So it is not inherit StackPanel directly. You could also custom a customrederer like the follow.
[assembly: ExportRenderer(typeof(StackLayout), typeof(ICustomStackLayoutRenderer))]
namespace CustomStackLayoutRenderer.UWP
{
public class ICustomStackLayoutRenderer : ViewRenderer<StackLayout, StackPanel>
{
private bool _childAdded;
protected override void OnElementChanged(ElementChangedEventArgs<StackLayout> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var stacklayout = new StackPanel();
SetNativeControl(stacklayout);
}
if (e.OldElement != null)
{
}
if (e.NewElement != null)
{
if (!_childAdded && Control.Children != null)
{
_childAdded = true;
Control.Children.Insert(0, new Windows.UI.Xaml.Shapes.Rectangle() { Width = 100, Height = 100, Fill = new SolidColorBrush(Colors.Red) });
}
}
}
}
}
For your requirement, the better way is that create a CustomStackLayout inherit StackLayout in Xamarin.Forms, and re-layout your children element in your LayoutChildren override method. For more detail you could refer Creating a Custom Layout.