I have a Web View inside Xamarin Forms content where I'm using plain html div, I want to navigate to another page (Navigation.PopAsync or Navigation.PushAsync) on div onClick(), is it possible without implementing any renderer or hybrid view?
You can create a HybridWebView to realize your function .
HybridWebView.cs:
public class HybridWebView : View
{
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);
}
}
Used in Xaml.cs :
<local:HybridWebView x:Name="hybridWebView" Uri="index.html" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
Then in Android , Create HybridWebViewRenderer.cs :
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, Android.Webkit.WebView>
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
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.LoadUrl($"file:///android_asset/Content/{Element.Uri}");
}
}
}
JavascriptWebViewClient.cs inside Renderer as follow:
public class JavascriptWebViewClient : WebViewClient
{
string _javascript;
public JavascriptWebViewClient(string javascript)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
Also need JSBridge to process JavaScript :
public class JSBridge : Java.Lang.Object
{
readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;
public JSBridge (HybridWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference <HybridWebViewRenderer> (hybridRenderer);
}
[JavascriptInterface]
[Export ("invokeAction")]
public void InvokeAction (string data)
{
HybridWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget (out hybridRenderer))
{
hybridRenderer.Element.InvokeAction (data);
}
}
}
Okey, index.html sample as follow :
<html>
<body>
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<h1>HybridWebView Test</h1>
<br/>
Enter name: <input type="text" id="name">
<br/>
<br/>
<button type="button" onclick="javascript:invokeCSCode($('#name').val());">Invoke C# Code</button>
<br/>
<p id="result">Result:</p>
<script type="text/javascript">
function log(str)
{
$('#result').text($('#result').text() + " " + str);
}
function invokeCSCode(data) {
try {
log("Sending Data:" + data);
invokeCSharpAction(data);
}
catch (err){
log(err);
}
}
</script>
</body>
</html>
After running it , the view is as follow:
Last , where we invoke function from JavaScript is in ContenPage:
hybridWebView.RegisterAction ( data => { Navigation.PushModalAsync(new PageNext()); });
The IOS HybridWebViewRenderer.cs class is as follow :
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
WKUserContentController userController;
protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged (e);
if (e.OldElement != null) {
userController.RemoveAllUserScripts ();
userController.RemoveScriptMessageHandler ("invokeAction");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup ();
}
if (e.NewElement != null) {
if (Control == null)
{
userController = new WKUserContentController();
var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript(script);
userController.AddScriptMessageHandler(this, "invokeAction");
var config = new WKWebViewConfiguration { UserContentController = userController };
var webView = new WKWebView(Frame, config);
SetNativeControl(webView);
}
string fileName = Path.Combine (NSBundle.MainBundle.BundlePath, string.Format ("Content/{0}", Element.Uri));
Control.LoadRequest (new NSUrlRequest (new NSUrl (fileName, false)));
}
}
public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message)
{
Element.InvokeAction (message.Body.ToString ());
}
}
Here also has a official sample for reference.
Related
While developing xamarin forms custom webview, when I try to load URL from assets folder, getting below error
[AndroidProtocolHandler] Unable to open asset URL: file:///android_asset/www/sf.min.js
This is my code
ChatbotView.xaml file
<controls:HybridWebView
x:Name="webView"
Uri="TestWebPage.html"
Margin="10"
BackgroundColor="Red"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand" />
This is ChatbotViewModel and HybridWebView class
public class ChatbotViewModel: BaseViewModel
{
public UrlWebViewSource SourceContent { get; set; }
public ChatbotViewModel()
{
var source = new UrlWebViewSource();
var baseUrl = DependencyService.Get<IBaseUrl>().Get();
string filePathUrl = Path.Combine(baseUrl, "TestWebPage.html");
source.Url = filePathUrl;
SourceContent = source;
}
}
public interface IBaseUrl { string Get(); }
public class HybridWebView : 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);
}
}
This is HybridWebViewRenderer, JavascriptWebViewClient and JSBridge class
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavascriptFunction = "function Chat1(data){jsBridge.invokeAction(data);}";
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((HybridWebView)Element).Cleanup();
}
if (e.NewElement != null)
{
Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.Settings.AllowFileAccess = true;
Control.Settings.JavaScriptEnabled = true;
Control.Settings.SetAppCacheMaxSize(100000000);
Control.Settings.AllowFileAccessFromFileURLs = true;
Control.Settings.AllowUniversalAccessFromFileURLs = true;
Control.Settings.AllowContentAccess = true;
Control.SetWebChromeClient(new WebChromeClient());
Control.SetWebViewClient(new WebViewClient());
var myUlr2 = $"file:///android_asset/TestWebPage.html";
Control.LoadUrl(myUlr2);
}
}
public class JavascriptWebViewClient : FormsWebViewClient
{
string _javascript;
public JavascriptWebViewClient(HybridWebViewRenderer renderer, string javascript) : base(renderer)
{
_javascript = javascript;
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
public class JSBridge : Java.Lang.Object
{
readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;
public JSBridge(HybridWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
HybridWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
((HybridWebView)hybridRenderer.Element).InvokeAction(data);
}
}
}
}
Below is TestWebpage.html file. This file is accessing one of JavaScript file from assets folder
<script type='text/javascript' src='file:///android_asset/www/sf.min.js'></script>
sf.min.js is not getting loaded and throwing error.
<html>
<body>
<button onclick="Chat1()">Submit</button>
<script type='text/javascript' src='file:///android_asset/www/sf.min.js'></script>
<script type='text/javascript'>
function Chat1() {
var initESW = function (gslbBaseURL) {
embedded_svc.settings.displayHelpButton = true; //Or false
embedded_svc.settings.language = ''; //For example, enter 'en' or 'en-US'
embedded_svc.settings.enabledFeatures = ['LiveAgent'];
embedded_svc.settings.entryFeature = 'LiveAgent';
embedded_svc.init(
'https://ulr.my.salesforce.com',
'https://ulr.force.com/visualforce',
gslbBaseURL,
'00D7a00000055uj',
'US_Universities',
{
'baseLiveAgentContentURL': 'https://c.la3-c1cs-cdg.salesforceliveagent.com/content',
'deploymentId': '720008Oqg',
'buttonId': '5730PID',
'baseLiveAgentURL': 'https://d.la3-c1cs-cdg.salesforceliveagent.com/chat',
'eswLiveAgentDevName': 'EmbeddedServiceLiveAgent_Parent0000000jLUAQ_17d9a605e8e',
'isOfflineSupportEnabled': false
}
);
};
if (!window.embedded_svc) {
var s = document.createElement('script');
console.log("Control here1")
var sdxMin = 'https://ulr.salesforce.com/embeddedservice/5.0/esw.min.js/'
console.log("Control here2")
s.src = sdxMin;
console.log("Control here3")
s.onload = function () {
initESW(null);
}
document.body.appendChild(s);
}
else {
initESW('https://service.force.com');
}
}
</script>
</body>
</html>
doc.microsoft webview, this I have followed for development. How can I fix this error ?
Edit 1 : Screenshot of Assets folder
From my perspective,you haven't put the sf.min.js file under Assets/www/sf.min.js folder.When compiling the TestWebpage.html, the system can't detect the js file as a result of the error "[AndroidProtocolHandler] Unable to open asset URL: file:///android_asset/www/sf.min.js".
I am trying to create a hybrid webview which is going to get its html data from the web api, the data which api is returning is in the form of string.
The problem which i am facing right now i am not able to bind or load the url of web on my hybrid webview i am not able to understand where the thing has to be change.
Here is the code which i have tried till now but could not make it work
here is custom control
public class HybridWebView : View
{
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);
}
}
Here is my custom renderer for Android
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, Android.Webkit.WebView>
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var webView = new Android.Webkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
webView.Settings.BuiltInZoomControls = true;
webView.Settings.DisplayZoomControls = false;
webView.Settings.LoadWithOverviewMode = true;
webView.Settings.UseWideViewPort = true;
Control.LoadUrl($"{Element.Uri}");
SetNativeControl(webView);
}
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
///Control.LoadUrl($"{Element.Uri}");
}
}}
Here is my IOS Renderer
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
WKUserContentController userController;
protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged (e);
if (Control == null) {
userController = new WKUserContentController ();
var script = new WKUserScript (new NSString (JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript (script);
userController.AddScriptMessageHandler (this, "invokeAction");
var config = new WKWebViewConfiguration { UserContentController = userController };
var webView = new WKWebView (Frame, config);
SetNativeControl (webView);
}
if (e.OldElement != null) {
userController.RemoveAllUserScripts ();
userController.RemoveScriptMessageHandler ("invokeAction");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup ();
}
if (e.NewElement != null) {
string fileName = Path.Combine (NSBundle.MainBundle.BundlePath, string.Format ("Content/{0}", Element.Uri));
Control.LoadRequest (new NSUrlRequest (new NSUrl (fileName, false)));
}
}
public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message)
{
Element.InvokeAction (message.Body.ToString ());
}
}
here is the code reference from i took this
this
this is where i am giving a source to webview from my webapi in my common library
var ee = await PolicyService.GetPrivacyPolicy();
var et = await PolicyService.GetTermsPolicy();
var htmlprivarcy = new HtmlWebViewSource
{
Html = ee
};
var htmlforterms = new HtmlWebViewSource
{
Html = et
};
Privacy.Uri = ee;
hybrid webview which is going to get its html data from the web api
You get HTML data, not a url, so
Android:
you shouldn't use Control.LoadUrl($"{Element.Uri}"); method when you load it,change it to Control.LoadData($"{Element.Uri}","text/html","utf-8");
ios:
change Control.LoadRequest to Control.LoadHtmlString($"{Element.Uri}", null);
I am using xamarin forms webview and want to pass username and password json body with url like below.
I Implement Hybrid webview in xamarin forms
My PCL Code:
public class HybridWebView : WebView
{
Action<string> action;
public EventHandler<bool> WebNavigating;
public void RegisterAction (Action<string> callback)
{
action = callback;
}
public void Cleanup ()
{
action = null;
}
public void InvokeAction (string data)
{
if (action == null || string.IsNullOrEmpty(data)) {
return;
}
action.Invoke (data);
}
}
My Android HybridWebViewRenderer Code :
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, Android.Webkit.WebView>
{
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var webView = new Android.Webkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.SetWebViewClient(new MyWebViewClient(webView,e,this));
SetNativeControl(webView);
}
}
}
public class MyWebViewClient : WebViewClient
{
const string JavaScriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
public Android.Webkit.WebView _webView;
public Android.Webkit.WebView WebView
{
get
{
return _webView;
}
}
public ElementChangedEventArgs<HybridWebView> _element;
public MyWebViewClient(Android.Webkit.WebView view, ElementChangedEventArgs<HybridWebView> element, HybridWebViewRenderer x)
{
_webView = view;
_element = element;
if (_element.OldElement != null)
{
_webView.RemoveJavascriptInterface("jsBridge");
var hybridWebView = _element.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (_element.NewElement != null)
{
_webView.AddJavascriptInterface(new JSBridge(x), "jsBridge");
string url = "https://Myspecific.com/Account/MobileLogin";
string postData = "{\"UserName\":\"User1\",\"Password\":\"pass123\",\"RememberMe\":false}";
_webView.PostUrl(url, Encoding.UTF8.GetBytes(postData));
}
}
output:
It shows wrong username and password
Please help...
Thanks in advance...
When open renderer page many times, when back to last page, it crashed and log this exception:
System.NotSupportedException: Unable to activate instance of type
AppZPMC.Droid.Renderers.CustomVideoWebViewRenderer from native handle
0xff9919cc (key_handle 0xf2ff0de).
Here is my code:
public class CustomVideoWebViewRenderer : WebViewRenderer
{
string url;
Activity activity;
WebView webView;
View customView;
CustomVideoWebView element;
FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
FrameLayout fullscreenContainer;
WebChromeClient.ICustomViewCallback customViewCallback;
public CustomVideoWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
webView = this.Control;
activity = CrossCurrentActivity.Current.Activity;
if (e.NewElement != null)
{
element = e.NewElement as CustomVideoWebView;
url = element.VideoUrl;
}
if (webView != null)
{
InitWebView();
}
}
protected override void OnDetachedFromWindow()
{
if (Element == null)
{
return;
}
base.OnDetachedFromWindow();
if (Control != null)
{
Control.StopLoading();
Control.LoadUrl("");
Control.Reload();
Control.Destroy();
}
}
public void FullScreen(Activity pActivity)
{
element.IsFullScreen = true;
WindowManagerLayoutParams attrs = pActivity.Window.Attributes;
attrs.Flags |= WindowManagerFlags.Fullscreen;
attrs.Flags |= WindowManagerFlags.KeepScreenOn;
pActivity.Window.Attributes = attrs;
pActivity.RequestedOrientation = Android.Content.PM.ScreenOrientation.Landscape;
}
public void SmallScreen(Activity pActivity)
{
element.IsFullScreen = false;
WindowManagerLayoutParams attrs = pActivity.Window.Attributes;
attrs.Flags &= ~WindowManagerFlags.Fullscreen;
attrs.Flags &= ~WindowManagerFlags.KeepScreenOn;
pActivity.Window.Attributes = attrs;
pActivity.RequestedOrientation = Android.Content.PM.ScreenOrientation.UserPortrait;
}
private void InitWebView()
{
WebChromeClient wvcc = new WebChromeClient();
WebSettings webSettings = webView.Settings;
webSettings.JavaScriptEnabled = true;
webSettings.SetSupportZoom(false);
webSettings.CacheMode = CacheModes.NoCache;
webView.SetWebViewClient(new VideoWebViewClient());
FrameLayout frameLayout = new FrameLayout(activity.ApplicationContext);
frameLayout.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
webView.SetWebChromeClient(new VideoWebChromeClient(frameLayout, ShowCustomView, HideCustomView));
webView.LoadUrl(url);
}
private class VideoWebViewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
{
view.LoadUrl(request.Url.ToString());
return base.ShouldOverrideUrlLoading(view, request);
}
}
private class VideoWebChromeClient : WebChromeClient
{
FrameLayout frameLayout;
Action HideCustomView;
Action<View, ICustomViewCallback> ShowCustomView;
public VideoWebChromeClient(FrameLayout frameLayout, Action<View, ICustomViewCallback> showCustomView, Action hideCustomView)
{
this.frameLayout = frameLayout;
ShowCustomView = showCustomView;
HideCustomView = hideCustomView;
}
public override View VideoLoadingProgressView => frameLayout;
public override void OnShowCustomView(View view, ICustomViewCallback callback)
{
ShowCustomView(view, callback);
base.OnShowCustomView(view, callback);
}
public override void OnHideCustomView()
{
HideCustomView();
base.OnHideCustomView();
}
}
private void ShowCustomView(View view, WebChromeClient.ICustomViewCallback callback)
{
if (customView != null)
{
callback.OnCustomViewHidden();
return;
}
FrameLayout decor = (FrameLayout)activity.Window.DecorView;
fullscreenContainer = new FullscreenHolder(activity.ApplicationContext);
fullscreenContainer.AddView(view, COVER_SCREEN_PARAMS);
decor.AddView(fullscreenContainer, COVER_SCREEN_PARAMS);
customView = view;
customViewCallback = callback;
FullScreen(activity);
}
private void HideCustomView()
{
if (customView == null)
{
return;
}
FrameLayout decor = (FrameLayout)activity.Window.DecorView;
decor.RemoveView(fullscreenContainer);
fullscreenContainer = null;
customView = null;
customViewCallback.OnCustomViewHidden();
webView.Visibility = ViewStates.Visible;
SmallScreen(activity);
}
private class FullscreenHolder : FrameLayout
{
public FullscreenHolder(Context ctx) : base(ctx)
{
SetBackgroundColor(Color.Black);
}
public override bool OnTouchEvent(MotionEvent e)
{
return true;
}
}
}
This is full crash log:
Java.Lang.Error: Exception of type 'Java.Lang.Error' was thrown.
java.lang.Error: Java callstack:
md5ff1c77b81adfd2c553f48967fc623ae5.CustomVideoWebViewRenderer.n_onDetachedFromWindow(Native Method)
md5ff1c77b81adfd2c553f48967fc623ae5.CustomVideoWebViewRenderer.onDetachedFromWindow(CustomVideoWebViewRenderer.java:45)
android.view.View.dispatchDetachedFromWindow(View.java:14595)
android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3074)
android.view.ViewGroup.removeAllViewsInLayout(ViewGroup.java:4792)
android.view.ViewGroup.removeAllViews(ViewGroup.java:4738)
md5270abb39e60627f0f200893b490a1ade.FragmentContainer.n_onDestroyView(Native Method)
md5270abb39e60627f0f200893b490a1ade.FragmentContainer.onDestroyView(FragmentContainer.java:59)
android.support.v4.app.Fragment.performDestroyView(Fragment.java:2480)
android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1422)
android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1569)
android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:753)
android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2415)
android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2201)
android.support.v4.app.FragmentManagerImpl.optimizeAndExecuteOps(FragmentManager.java:2155)
android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2064)
android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:718)
android.os.Handler.handleCallback(Handler.java:742)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:157)
android.app.ActivityThread.main(ActivityThread.java:5551)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:742)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:632)
Its common problem (bug) in Xamarin.Forms, take a look at: https://github.com/xamarin/Xamarin.Forms/issues/1646, no fix yet :(
Is there a way to detect a long tap on an image control in Xamarin Forms?
I'm using the carousel view to display images and would like to give the option to delete them by selecting with a long tap.
Based on your suggestions in the comments this is what I did:
(The purpose of the control is to be able to select an Image with a LongTap)
I defined my own Image control in the PCL:
IsSelected BindableProperty.
LongTap event.
public class MyImage:Image
{
private BindableProperty IsSelectedProperty = BindableProperty.Create("IsSelected", typeof(bool), typeof(MyImage), false);
public bool IsSelected {
get {
return (bool)GetValue(IsSelectedProperty);
}
set {
SetValue(IsSelectedProperty, value);
}
}
public event EventHandler LongClick;
public void OnLongClick()
{
IsSelected = !IsSelected;
if(IsSelected)
{
Opacity = 0.5;
}
else
{
Opacity = 1;
}
if (LongClick != null)
{
LongClick(this, EventArgs.Empty);
}
}
}
And this is my custom renderer: (Defined in the Android project)
[assembly: ExportRenderer(typeof(MyImage), typeof(MyImageRenderer))]
namespace PRISMCarouselView.Droid.Renderes
{
public class MyImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (Control != null)
{
ImageView androidSource = Control as ImageView;
MyImage myImage = e.NewElement as MyImage;
androidSource.LongClick += (object sender, LongClickEventArgs ee) =>
{
myImage.OnLongClick();
};
}
}
}
}
Edit 1:
Here's a slightly updated version, I use the BindingPropertyChangedDelegate to change the opacity of the image:
public class SelectableImage : Image
{
public SelectableImage()
{
}
private static BindableProperty IsSelectedProperty = BindableProperty.Create("IsSelected",
typeof(bool),
typeof(SelectableImage),
false, BindingMode.Default, null, (sender, o1, o2) => {
SelectableImage imageControl = sender as SelectableImage;
if(imageControl != null)
{
if(imageControl.IsSelected)
{
imageControl.Opacity = 0.5;
}else
{
imageControl.Opacity = 1;
}
}
});
public bool IsSelected {
get {
return (bool)GetValue(IsSelectedProperty);
}
set {
SetValue(IsSelectedProperty, value);
}
}
}
And the renderer:
[assembly: ExportRenderer(typeof(SelectableImage), typeof(SelectableImageRenderer))]
namespace Muserma.Apps.Droid.Renderer
{
public class SelectableImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (Control != null)
{
ImageView androidSource = Control as ImageView;
SelectableImage selectableImage = e.NewElement as SelectableImage;
androidSource.LongClick += (object sender, LongClickEventArgs ee) =>
{
selectableImage.IsSelected = !selectableImage.IsSelected;
};
}
}
}
}