dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.04k stars 1.73k forks source link

Existing TextDecorations applied to a Label are not removed when a new TextDecoration value is set after the Label's Text has been modified #16967

Open Unflab opened 1 year ago

Unflab commented 1 year ago

Description

Title is a bit of a mouthful, but the steps to reproduce clearly lay it out.

I'm trying to implement logic that strikes through the Text of a Label when a Checkbox is checked. Unchecking the checkbox should remove the strikethrough (by setting TextDecorations.None).

This works fine on Android, and works fine on iOS until the Label's text changes while a Strikethrough is active. Unchecking the checkbox after that does not remove the strikethrough.

Steps to Reproduce

Run linked repo on iOS

  1. Tap button to change Label text
  2. Check checkbox to apply strikethrough effect
  3. Uncheck checkbox to see strikethrough effect disappear and underline effect appear <- this is expected behaviour

Re-run the project

  1. Tap button to change Label text
  2. Check checkbox to apply strikethrough effect
  3. Tap button again to change Label text
  4. Uncheck checkbox to see strikethrough effect does NOT disappear and underline effect appear <- expect to see strikethrough disappear at this point.

Link to public reproduction project repository

https://github.com/Unflab/StrikethroughIssue.git

Version with bug

7.0.92

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

iOS

Affected platform versions

No response

Did you find any workaround?

Edited 22 September: repo has been updated with functioning workaround

Relevant log output

No response

ghost commented 1 year ago

We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.

XamlTest commented 1 year ago

Verified this on Visual Studio Enterprise 17.8.0 Preview 1.0. Repro on iOS 16.4 .NET 8, not repro on Android 13.0-API33 with below Project: StrikethroughIssue.zip

image

SyntaxDomain commented 1 year ago

The problem appears to be from mixing UILabel.Text and UILabel.AttributeText. I was able to get around it by overriding MapText in a custom LabelHandler and setting the AttributeText instead of letting Maui set the Text field. This may have other side effects, but it didn't affect anything important to me.

Here is my handler:

public partial class StrikethroughLabelHandler : LabelHandler
{
    public new static IPropertyMapper<ILabel, ILabelHandler> Mapper =
        new PropertyMapper<ILabel, ILabelHandler>(ViewMapper)
        {
            ["Background"] = MapBackground,
            ["Opacity"] = MapOpacity,
            ["CharacterSpacing"] = MapCharacterSpacing,
            ["Font"] = MapFont,
            ["HorizontalTextAlignment"] = MapHorizontalTextAlignment,
            ["VerticalTextAlignment"] = MapVerticalTextAlignment,
            ["LineHeight"] = MapLineHeight,
            ["Padding"] = MapPadding,
            ["Text"] = StrikethroughLabelHandler.MapText,
            ["TextColor"] = MapTextColor,
            ["TextDecorations"] = MapTextDecorations
        };

    public StrikethroughLabelHandler()
        : base(Mapper)
    {
    }

    public StrikethroughLabelHandler(IPropertyMapper? mapper)
        : base(mapper ?? Mapper, CommandMapper)
    {
    }

    public StrikethroughLabelHandler(IPropertyMapper? mapper, Microsoft.Maui.CommandMapper? commandMapper)
        : base(mapper ?? Mapper, commandMapper ?? CommandMapper)
    {
    }

    protected override MauiLabel CreatePlatformView() => new MauiLabel();

    public static void MapText(ILabelHandler handler, ILabel label)
    {
        MauiLabel platformView = handler.PlatformView;

        if (platformView != null)
            platformView.AttributedText = new NSMutableAttributedString(label.Text);

        LabelHandler.MapFormatting(handler, label);
    }
}

For anyone attempting to fix this in the Maui source, the specific line to change is here:

namespace Microsoft.Maui.Platform
{
    public static class TextViewExtensions
    {
        public static void UpdateTextPlainText(this TextView textView, IText label)
        {
            textView.Text = label.Text; //Should be textView.AttributeText = label.Text;
        }
        ...
    }
}
cl3m commented 1 year ago

Thank you @SyntaxDomain for the mapper. Some properties are not handled though so I had some problem with it. Here is a more robust fix :

public partial class StrikethroughLabelHandler : LabelHandler
{
    private static IPropertyMapper<ILabel, ILabelHandler> Mapper()
    {
        var mapper = new PropertyMapper<ILabel, ILabelHandler>(LabelHandler.Mapper);
        mapper.ModifyMapping(nameof(ILabel.Text), (h, v, a) => MapText(h, v));
        return mapper;
    }

    public StrikethroughLabelHandler() : base(Mapper()) { }

    public static void MapText(ILabelHandler handler, ILabel label)
    {
        if (handler.PlatformView != null && label.Text != null)
            handler.PlatformView.AttributedText = new NSMutableAttributedString(label.Text);

        MapFormatting(handler, label);
    }
}
Unflab commented 1 year ago

Thanks @cl3m. I've applied your workaround to the repo linked in my initial bug report, and it's now behaving as expected.