Is ContentEdgeInsets broken now? - ios

In the past i've written a small renderer for buttons to maintain a padding property on my forms element. Lately it stopped working and while debugging i noticed it says unknown member all of a sudden (which would explain why it has no effect anymore)
[assembly: ExportRenderer(typeof(EnhancedButton), typeof(EnhancedButtonRenderer))]
namespace MyNamespace
{
public class EnhancedButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
UpdatePadding();
}
private void UpdatePadding()
{
var element = this.Element as EnhancedButton;
if (element != null && this.Control != null)
{
this.Control.ContentEdgeInsets = new UIEdgeInsets(
(int)element.Padding.Top,
(int)element.Padding.Left,
(int)element.Padding.Bottom,
(int)element.Padding.Right
);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(EnhancedButton.Padding))
{
UpdatePadding();
}
}
}
}
pcl:
public class EnhancedButton : Button
{
#region Padding
public static BindableProperty PaddingProperty = BindableProperty.Create<EnhancedButton, Thickness>(d => d.Padding, default(Thickness));
public Thickness Padding
{
get { return (Thickness) GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
#endregion Padding
}
Is anyone aware of a workaround? Has the support of this property been canceled on ios side?
Xamarin.Forms is 2.1.0.6524. If i recall correctly it worked just fine a couple versions ago.

Related

How to approach writing a WPF Custom Control in F#?

I am trying to understand how a wpf custom control could be written in F#.
As an example, I have the following C# code for a drag and drop on a canvas (in C#). It inherits from ListBox. I'm not looking for anybody to rewrite this. But I'm at a loss as to how it would be implemented in Elmish.wpf since there is no xaml to deal with. (I believe a Custom Control does not have a XAML interface).
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Stargate.XI.Client.Views.CustomControls
{
public delegate void DropCompletedEventHandler(object sender, DropCompletedEventArgs e);
// To add a custom DropCompletedEvent to an ItemsControl, I would either have to have an attached property, as in
// https://stackoverflow.com/questions/15134514/attached-behavior-handling-an-attached-event-in-wpf
// or subclass an ItemsControl as below. Creating a simple custom control, like here, seems cleaner.
// Note: ItemsControl can't select items, only present collections. Only a Selector or one of it's descendants can select items
// Hence, only the ListBox or its derivative,ListView, have Selector's.
public class ChartCanvas : ListBox
{
public event EventHandler PlayMusicEvent;
public event EventHandler PauseMusicEvent;
public event EventHandler StopMusicEvent;
public event EventHandler DisposeMusicEvent;
public event EventHandler DisposePosterEvent;
#region DropCompletedEvent
// Create a custom routed event by first registering a RoutedEventID
// This event uses the bubbling routing strategy
public static readonly RoutedEvent DropCompletedEvent = EventManager.RegisterRoutedEvent(
"DropCompleted", RoutingStrategy.Bubble, typeof(DropCompletedEventHandler), typeof(ChartCanvas));
// Provide CLR accessors for the event. The RoutedEventHandler, e.g., "DropCompleted" is used in the xaml declaration for the ImageCanvas.
public event DropCompletedEventHandler DropCompleted
{
add { AddHandler(DropCompletedEvent, value); }
remove { RemoveHandler(DropCompletedEvent, value); }
}
// This method raises the DropCompleted event
public void RaiseDropCompletedEvent(object datatype)
{
RaiseEvent(new DropCompletedEventArgs(DropCompletedEvent, datatype));
}
#endregion
public ChartCanvas()
{
AllowDrop = true;
DragEnter += IC_DragEnter;
Drop += IC_Drop;
DragOver += IC_DragOver;
DragLeave += IC_DragLeave;
}
private void IC_DragLeave(object sender, DragEventArgs e)
{
e.Handled = true;
}
private void IC_DragOver(object sender, DragEventArgs e)
{
e.Handled = true;
}
private void IC_Drop(object sender, DragEventArgs e)
{
var data = e.Data.GetData(DataFormats.Text);
var dragSource = e.Data.GetData("DragSource");
RaiseDropCompletedEvent(data);
}
private void IC_DragEnter(object sender, DragEventArgs e)
{
e.Handled = true;
}
#region PlayMovie
private ICommand _playMovie;
public ICommand PlayMovieCommand
{
get
{
if (_playMovie == null)
{
_playMovie = new RelayCommand(
p => true,
p => this.PlayMovie());
}
return _playMovie;
}
}
private void PlayMovie()
{
PlayMusicEvent?.Invoke(this, EventArgs.Empty);
}
#endregion
#region PauseMovie
private ICommand _pauseMovie;
public ICommand PauseMovieCommand
{
get
{
if (_pauseMovie == null)
{
_pauseMovie = new RelayCommand(
p => true,
p => this.PauseMovie());
}
return _pauseMovie;
}
}
private void PauseMovie()
{
PauseMusicEvent?.Invoke(this, EventArgs.Empty);
}
#endregion
#region StopMovie
private ICommand _stopMovie;
public ICommand StopMovieCommand
{
get
{
if (_stopMovie == null)
{
_stopMovie = new RelayCommand(
p => true,
p => this.StopMovie());
}
return _stopMovie;
}
}
private void StopMovie()
{
StopMusicEvent?.Invoke(this, EventArgs.Empty);
}
#endregion
public bool Dispose
{
get { return (bool)GetValue(DisposeProperty); }
set { SetValue(DisposeProperty, value); }
}
// Using a DependencyProperty as the backing store for Dispose. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisposeProperty =
DependencyProperty.Register("Dispose", typeof(bool), typeof(ChartCanvas), new PropertyMetadata(false,
(s,e) =>
{
ChartCanvas chartcanvas = s as ChartCanvas;
chartcanvas.DisposeMusicEvent?.Invoke(chartcanvas, EventArgs.Empty);
chartcanvas.DisposePosterEvent?.Invoke(chartcanvas, EventArgs.Empty);
}
));
}
}
Any suggestions to this newbie as to how to approach this would be much appreciated.
TIA

Custom MvxRecyclerAdapter

I am using MvvmCross in my Xamarin.Android application. I want to make my own custom MvxRecyclerAdapter so that I can have multiple buttons in each row of the MvxRecyclerView. Here is my custom MvxRecyclerView:
public class TwoPieceMvxRecyclerView : MvxRecyclerView
{
private bool _initialized;
public TwoPieceMvxRecyclerView(Context context, IAttributeSet attr) : base(context, attr)
{
}
public override Android.Support.V7.Widget.RecyclerView.Adapter GetAdapter()
{
if(!_initialized)
{
SetAdapter(new TwoPieceMvxRecyclerAdapter());
_initialized = true;
}
return base.GetAdapter();
}
}
And here is my custom MvxRecyclerAdapter:
public class TwoPieceMvxRecyclerAdapter : MvxRecyclerAdapter, IOnClickListener
{
private ICommand _itemClickPiece1;
private ICommand _itemClickPiece2;
private View _clickablePiece1;
private View _clickablePiece2;
public TwoPieceMvxRecyclerAdapter()
{
}
public ICommand ItemClickPiece1
{
get { return _itemClickPiece1; }
set
{
if (ReferenceEquals(_itemClickPiece1, value))
{
return;
}
_itemClickPiece1 = value;
}
}
public ICommand ItemClickPiece2
{
get { return _itemClickPiece2; }
set
{
if (ReferenceEquals(_itemClickPiece2, value))
{
return;
}
_itemClickPiece2 = value;
}
}
protected override Android.Views.View InflateViewForHolder(Android.Views.ViewGroup parent, int viewType, MvvmCross.Binding.Droid.BindingContext.IMvxAndroidBindingContext bindingContext)
{
var view = base.InflateViewForHolder(parent, viewType, bindingContext);
_clickablePiece1 = view.FindViewById<View>(Resource.Id.clickable_piece1);
_clickablePiece2 = view.FindViewById<View>(Resource.Id.clickable_piece2);
_clickablePiece1.SetOnClickListener(this);
_clickablePiece2.SetOnClickListener(this);
return view;
}
public void OnClick(View v)
{
if (v == _clickablePiece1)
{
ItemClickPiece1.Execute(null);
}
else if (v == _clickablePiece2)
{
ItemClickPiece2.Execute(null);
}
}
}
When I run the application I get this error:
Could not activate JNI Handle 0xbfd00978 (key_handle 0x6e44919) of
Java type
'md5bd77c484e80df14e69d8c5ab04394fe0/TwoPieceMvxRecyclerView' as
managed type
'AzzimovMobile.Droid.Components.TwoPieceMvxRecycler.TwoPieceMvxRecyclerView'.
System.InvalidOperationException: If you wan't to use single
item-template RecyclerView Adapter you can't change
it'sIMvxTemplateSelector to anything other than
MvxDefaultTemplateSelector
You are missing a constructor on your RecyclerView:
public TwoPieceMvxRecyclerView(IntPtr javaReference, JniHandleOwnership transfer): base(javaReference, transfer)
{
}
Also be aware you don't need to use a custom RecyclerView to change its Adapter. You can just grab the RecyclerView instance on your .cs view and set the adapter from there. Something like this should work:
public class MyView: MvxFragment<MyViewModel>
{
//...
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = base.OnCreateView(inflater, container, savedInstanceState);
// ...
var recycler = view.FindViewById<MvxRecyclerView>(Resource.Id.recycler);
recycler.Adapter = new TwoPieceMvxRecyclerAdapter(((IMvxAndroidBindingContext)BindingContext);
// you can even set a TemplateSelector here!
recycler.ItemTemplateSelector = new MyTemplateSelector();
// ...
return view;
}
}

Xamarin Forms iOS CustomRenderer not repainting

I have a custom renderer to display HTML formatted text in a UITextView. If I hard-code the text into the constructor of the page that contains the control (so it gets set in the custom control's OnElementChanged event), it displays fine. If I await a api call to get the text and then set it (so it gets set in the custom control's OnElementPropertyChanged event) it does not repaint. If I change the orientation of the device, the text appears. What do I need to add to get it to display the text when it is set?
[assembly: ExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace MyApp.iOS.Renderers
{
class HtmlLabelRenderer : ViewRenderer<HtmlLabel, UITextView>
{
private UITextView _htmlTextView = new UITextView();
protected override void OnElementChanged(ElementChangedEventArgs<HtmlLabel> e)
{
base.OnElementChanged(e);
if (Element?.Text == null) return;
SetHtmlText(Element.Text);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (string.Equals(e.PropertyName, "Text", StringComparison.CurrentCultureIgnoreCase))
{
SetHtmlText(((HtmlLabel)sender).Text);
_htmlTextView.SetNeedsDisplay();
}
base.OnElementPropertyChanged(sender, e);
}
private void SetHtmlText(string text)
{
var attr = new NSAttributedStringDocumentAttributes {DocumentType = NSDocumentType.HTML};
var nsError = new NSError();
_htmlTextView.Editable = false;
_htmlTextView.AttributedText = new NSAttributedString(text, attr, ref nsError);
_htmlTextView.DataDetectorTypes = UIDataDetectorType.All;
SetNativeControl(_htmlTextView);
}
}
}
Update : I got further by changing the OnElementChanged to:
protected override void OnElementChanged(ElementChangedEventArgs<HtmlLabel> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null) return;
SetHtmlText(e.NewElement.Text ?? string.Empty);
SetNativeControl(_htmlTextView);
}
now if I have more than one HtmlLabel on the page all except the first one displays.
Try changing from:
_htmlTextView.SetNeedsDisplay();
to
SetNeedsDisplay();
or,
(Control ?? NativeView).SetNeedsDisplay();
Sharada Gururaj is right, changing to derive from Editor worked for iOS .. but breaks Android. Though this seems brute force, I used conditional compilation to get it working...
namespace MyApp.Renderers
{
#if __IOS__
public class HtmlLabel : Editor
{
}
#else
public class HtmlLabel : Label
{
}
#endif
}
Here is the android
[assembly: ExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace MyApp.Droid.Renderers
{
public class HtmlLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (HtmlLabel)Element;
if (view?.Text == null) return;
SetHtmlText(view.Text);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Label.TextProperty.PropertyName)
{
SetHtmlText(((HtmlLabel) sender).Text);
}
}
private void SetHtmlText(string text)
{
var encodedText = (((int)Build.VERSION.SdkInt) >= 24) ? Html.FromHtml(text, FromHtmlOptions.ModeLegacy) :
#pragma warning disable 618
// need this for backward compatability
Html.FromHtml(text);
#pragma warning restore 618
Control.MovementMethod = LinkMovementMethod.Instance;
Control.SetText(encodedText, TextView.BufferType.Spannable);
}
}
}
Your custom HtmlLabel class should be able to derive from the same thing on Android and iOS
namespace YourNameSpace
{
public class HtmlLabel : Label
{
}
}
The renderer for Android should look something like this
[assembly: ExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace YourNameSpace.Droid
{
public class HtmlLabelRenderer : ViewRenderer<Label, TextView>
{
TextView _textView;
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
if(Control == null)
{
_textView = new TextView(Context);
SetHtmlText(Element.Text);
SetNativeControl(_textView);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Element == null || Control == null)
return;
if (e.PropertyName == HtmlLabel.TextProperty.PropertyName)
{
SetHtmlText(Element.Text);
}
}
private void SetHtmlText(string text)
{
if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.N)
{
_textView.TextFormatted = Html.FromHtml(text, Android.Text.FromHtmlOptions.ModeCompact);
}
else
{
_textView.TextFormatted = Html.FromHtml(text);
}
}
}
}
And on iOS it should look very similar
[assembly: ExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace YourNameSpace.iOS
{
public class HtmlLabelRenderer : ViewRenderer<Label, UITextView>
{
UITextView _textView;
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
if(Control == null)
{
_textView = new UITextView();
SetHtmlText(Element.Text);
SetNativeControl(_textView);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Element == null || Control == null)
return;
if (e.PropertyName == HtmlLabel.TextProperty.PropertyName)
{
SetHtmlText(Element.Text);
}
}
private void SetHtmlText(string text)
{
var attr = new NSAttributedStringDocumentAttributes { DocumentType = NSDocumentType.HTML };
var nsError = new NSError();
_textView.Editable = false;
_textView.AttributedText = new NSAttributedString(text, attr, ref nsError);
_textView.DataDetectorTypes = UIDataDetectorType.All;
}
}
}
I tested that on Android and it worked, calling OnElementPropertyChanged when the text changed and everything. However, I don't have a mac at home to try the iOS Renderer so I'm just assuming it will function pretty much the same.

How to get the progress of WebView while loading the data, Xamarin.Forms

I am developing an App using Xamarin.Forms for listing the news from different sources. I use a webView to open the link corresponding to the news. But I want to show the progress while loading the webpage into web view, like the progress bar on Safari App. For this I have used the ProgressBar element like this:
<StackLayout>
<!-- WebView needs to be given height and width request within layouts to render. -->
<ProgressBar Progress ="" HorizontalOptions="FillAndExpand" x:Name="progress"/>
<WebView x:Name="webView"
HeightRequest="1000"
WidthRequest="1000"
VerticalOptions= "FillAndExpand"
Navigating="webOnNavigating"
Navigated="webOnEndNavigating"/>
</StackLayout>
and in the code I have used
void webOnNavigating (object sender, WebNavigatingEventArgs e)
{
progress.IsVisible = true;
}
void webOnEndNavigating (object sender, WebNavigatedEventArgs e)
{
progress.IsVisible = false;
}
But I want to show also the progress of loading the data, not just an indication that is loading and load. I want the user to know that the data are loading. Is there a way to achieve this.
The implementations should be platform specific via custom renders. Luckily this topics has been discussed already for different platforms here on SO.
The Android version based on this thread:
[assembly: ExportRenderer(typeof(WebView), typeof(GenericWebViewRenderer))]
namespace WebViewWithProgressBar.Droid
{
public class GenericWebViewRenderer : WebViewRenderer
{
Context ctx;
public GenericWebViewRenderer(Context context) : base(context)
{
ctx = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var progressBar = new Android.Widget.ProgressBar(ctx, null, Android.Resource.Attribute.ProgressBarStyleHorizontal);
Control.SetWebChromeClient(new MyWebChromeClient(progressBar));
Control.AddView(progressBar);
}
class MyWebChromeClient : Android.Webkit.WebChromeClient
{
Android.Widget.ProgressBar progressBar;
public MyWebChromeClient(Android.Widget.ProgressBar progressBar)
{
this.progressBar = progressBar;
}
public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
{
progressBar.SetProgress(newProgress, true);
}
}
}
}
On iOS it is a bit trickier, here is a very simple mock that does it job pretty well:
[assembly: ExportRenderer(typeof(WebView), typeof(GenericWebViewRenderer))]
namespace WebViewWithProgressBar.iOS
{
public class GenericWebViewRenderer : ViewRenderer<WebView, UIWebView>
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var progressBar = new UIProgressView(UIProgressViewStyle.Bar);
progressBar.TintColor = UIColor.Green;
progressBar.TrackTintColor = UIColor.Black;
progressBar.ProgressTintColor = UIColor.Red;
var webView = new UIWebView(Frame);
webView.AddSubview(progressBar);
SetNativeControl(webView);
Control.Delegate = new MyUIWebViewDelegate(progressBar);
webView.LoadRequest(new NSUrlRequest(new NSUrl("https://google.com")));
}
}
class MyUIWebViewDelegate : UIWebViewDelegate
{
UIProgressView progressBar { get; }
public MyUIWebViewDelegate(UIProgressView progressBar)
{
this.progressBar = progressBar;
}
public override void LoadStarted(UIWebView webView)
{
progressBar.SetProgress(0.1f, false);
}
public override void LoadingFinished(UIWebView webView)
{
progressBar.SetProgress(1.0f, true);
}
public override void LoadFailed(UIWebView webView, NSError error)
{
// TODO:
}
}
}
}
For more details please check here.
P.S.: This code examples are available on github.

Can I set custom onClick on my timeline using fabric sdk?

I am creating a Twitter client using Fabric but I can not create a custom onClick.
I created this custom adapter and tried to create a OnClickListener but not working. Always open in browser tweet.
public class TweetAdapter extends TweetTimelineListAdapter {
public ArrayList<Long> tweetIds=new ArrayList<Long>();
public TweetAdapter(Context context, Timeline<Tweet> timeline) {
super(context, timeline);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Object rowView = convertView;
final Tweet tweet = (Tweet)this.getItem(position);
if(convertView == null) {
rowView = new CompactTweetView(this.context, tweet);
} else {
((BaseTweetView)convertView).setTweet(tweet);
((View)rowView).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
tweetIds.add(tweet.getId());
}
});
}
return (View)rowView;
}
}
In BaseTweetView class it is the function type OnClickListener but in this case I can't think of any idea to overwrite.
private void setPermalinkLauncher() {
BaseTweetView.PermalinkClickListener listener = new BaseTweetView.PermalinkClickListener();
this.setOnClickListener(listener);
this.contentView.setOnClickListener(listener);
}
class PermalinkClickListener implements OnClickListener {
PermalinkClickListener() {
}
public void onClick(View v) {
if(BaseTweetView.this.getPermalinkUri() != null) {
BaseTweetView.this.scribePermalinkClick();
BaseTweetView.this.launchPermalink();
}
}
}
Any ideas? Thanks
Finally I made this works using a custom Adapter (very similar that the one you use in the question). This adapter obtains the resulting view from super implementation and adds an onClickListener to overrides the fabric defaults one:
class CustomTweetTimelineListAdapter extends TweetTimelineListAdapter {
public CustomTweetTimelineListAdapter(Context context, Timeline<Tweet> timeline) {
super(context, timeline);
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
//disable subviews to avoid links are clickable
if(view instanceof ViewGroup){
disableViewAndSubViews((ViewGroup) view);
}
//enable root view and attach custom listener
view.setEnabled(true);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String tweetId = "click tweetId:"+getItemId(position);
Toast.makeText(context, tweetId, Toast.LENGTH_SHORT).show();
}
});
return view;
}
//helper method to disable subviews
private void disableViewAndSubViews(ViewGroup layout) {
layout.setEnabled(false);
for (int i = 0; i < layout.getChildCount(); i++) {
View child = layout.getChildAt(i);
if (child instanceof ViewGroup) {
disableViewAndSubViews((ViewGroup) child);
} else {
child.setEnabled(false);
child.setClickable(false);
child.setLongClickable(false);
}
}
}
}
Full code example here.
Hope it helps.

Resources