0

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.

Christine
  • 562
  • 3
  • 19

3 Answers3

0

Try changing from:

_htmlTextView.SetNeedsDisplay();

to

SetNeedsDisplay();

or,

(Control ?? NativeView).SetNeedsDisplay();
Sharada Gururaj
  • 13,471
  • 1
  • 22
  • 50
  • Ok - just to confirm, is the `Text` property on `HtmlLabel` a bindable property? If you can, can you post the code for `HtmlLabel`. – Sharada Gururaj May 10 '17 at 19:54
  • No, there is no code for HtmlLabel - public class HtmlLabel : Label { } I add it to a page then set the text in the code behind after retrieving it HtmlDescription.Text = announcement.Details; – Christine May 10 '17 at 19:58
  • Ah! that might be the disconnect here. When I derive from an existing forms control - I try to either derive from the official forms renderer or at-least make sure the type parameter for Native control is same as used by Forms. I think in this case you should derive from [`Editor`](https://developer.xamarin.com/api/type/Xamarin.Forms.Editor/) instead of `Label`. [Ref for more details](https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/renderers/#Views) – Sharada Gururaj May 10 '17 at 20:10
  • On the android side I do use a label - public class HtmlLabelRenderer : LabelRenderer - how will changing this to an editor affect that? I'd really rather use the Label in iOS as well, but although the format nicely tapping them does nothing... – Christine May 10 '17 at 20:40
  • If you change to `Editor`, you will change to `EditText` in Android as mentioned in the [link] (https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/renderers/#Views). Please do note - its only speculation that this might be the root cause for this particular problem. – Sharada Gururaj May 10 '17 at 20:44
0

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.

Nick Peppers
  • 3,161
  • 23
  • 27
0

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);
        }
    }
}
Christine
  • 562
  • 3
  • 19
  • I updated my answer and you should be able to derive from the same thing on both platforms and just use `ViewRenderer – Nick Peppers May 11 '17 at 02:39