You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI Editor未编辑时文本截断及iOS适配问题求助

解决Xamarin.Forms自定义TruncatedEditor的iOS适配问题

需求与现有实现

需求:Editor控件在非编辑状态下显示2-3行带省略号的文本,进入编辑状态时展示完整文本。以下是自定义控件的实现代码:

C#自定义TruncatedEditor控件

public class TruncatedEditor : Editor
{
    private string _fullText;
    private const int MaxCharsForTruncatedText = 100;
    

    public TruncatedEditor()
    {
        this.Focused += TruncatedEditor_Focused;
        this.Unfocused += TruncatedEditor_Unfocused;
        this.AutoSize = EditorAutoSizeOption.TextChanges;
    }

    private void TruncatedEditor_Unfocused(object sender, FocusEventArgs e)
    {
        // Store the full text
        _fullText = Text;

        // Truncate text with ellipsis
        if (_fullText != null && _fullText.Length > MaxCharsForTruncatedText)
        {
            Text = _fullText.Substring(0, MaxCharsForTruncatedText - 3) + "...";
        }

        InvalidateMeasure(); 
    }

    private void TruncatedEditor_Focused(object sender, FocusEventArgs e)
    {
        Text = _fullText;

        if (!string.IsNullOrEmpty(Text))
        {
            CursorPosition = Text.Length;
            SelectionLength = 0;
        }

        InvalidateMeasure();
    }

    protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
    {
        // Measure the height based on the current text and width constraint
        var size = base.OnMeasure(widthConstraint, heightConstraint);
        var textLength = Text?.Length ?? 0;

        // Estimate the number of lines needed based on character count and width
        int charsPerLine = (int)(widthConstraint / 7); // Adjust average character width as needed
        int lineCount = (int)Math.Ceiling((double)textLength / charsPerLine);

        // Adjust height based on the estimated number of lines
        double newHeight = lineCount * size.Request.Height;
        return new SizeRequest(new Size(size.Request.Width, newHeight));
    }
}

XAML使用方式

<ctrls:TruncatedEditor
    x:Name="customEditor"
    Placeholder="Enter here...."
    HorizontalOptions="Fill"
    Text="{Binding Source={x:Reference customEditor},Path=Text}"/>

iOS平台存在的问题

  1. 获取焦点时,光标未定位到文本末尾,而是随机处于文本中间
  2. OnMeasure方法未被调用,导致控件高度无法正确调整

问题解决方案

1. 修复光标位置问题

iOS平台下,直接设置CursorPosition可能因文本更新后的控件渲染延迟失效,可通过两种方式解决:

方案一:延迟执行光标定位

修改TruncatedEditor_Focused方法,将光标定位逻辑放到主线程延迟执行:

private void TruncatedEditor_Focused(object sender, FocusEventArgs e)
{
    Text = _fullText;

    if (!string.IsNullOrEmpty(Text))
    {
        // 延迟执行确保文本已完成更新
        Device.BeginInvokeOnMainThread(() =>
        {
            CursorPosition = Text.Length;
            SelectionLength = 0;
        });
    }

    InvalidateMeasure();
    ForceLayout(); // 强制触发布局更新
}

方案二:iOS自定义Renderer

创建iOS平台的自定义Renderer,直接操作原生UITextView的选中范围:

[assembly: ExportRenderer(typeof(TruncatedEditor), typeof(TruncatedEditorRenderer))]
namespace YourAppNamespace.iOS.Renderers
{
    public class TruncatedEditorRenderer : EditorRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
        {
            base.OnElementChanged(e);

            if (Control != null)
            {
                Control.DidBeginEditing += Control_DidBeginEditing;
            }
        }

        private void Control_DidBeginEditing(object sender, EventArgs e)
        {
            var textView = sender as UITextView;
            if (textView != null && !string.IsNullOrEmpty(textView.Text))
            {
                textView.SelectedRange = new NSRange(textView.Text.Length, 0);
            }
        }
    }
}

2. 修复OnMeasure未调用问题

iOS平台下Editor的AutoSize行为与Android不同,InvalidateMeasure()可能无法触发测量,需做以下调整:

  • 在Focused和Unfocused事件中添加ForceLayout()强制触发布局测量
  • 替换原字符数估算行数的逻辑,改用更准确的文本尺寸计算方式

跨平台文本行数计算辅助方法

private int CalculateLineCount(string text, double widthConstraint)
{
    if (string.IsNullOrEmpty(text)) return 0;

    var formattedText = new FormattedString();
    formattedText.Spans.Add(new Span { Text = text, Font = this.Font });

    var textSize = TextManager.MeasureText(formattedText, widthConstraint, double.PositiveInfinity);
    var lineHeight = TextManager.MeasureText("A", widthConstraint, double.PositiveInfinity).Height;

    return (int)Math.Ceiling(textSize.Height / lineHeight);
}

修改OnMeasure方法

protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
    var size = base.OnMeasure(widthConstraint, heightConstraint);
    var lineCount = CalculateLineCount(Text, widthConstraint);

    // 非编辑状态限制最多3行,编辑状态不限制
    if (!IsFocused)
    {
        lineCount = Math.Min(lineCount, 3);
    }

    double newHeight = lineCount * size.Request.Height;
    return new SizeRequest(new Size(size.Request.Width, newHeight));
}

更新焦点事件方法

在TruncatedEditor_Unfocused中添加ForceLayout():

private void TruncatedEditor_Unfocused(object sender, FocusEventArgs e)
{
    _fullText = Text;

    if (_fullText != null && _fullText.Length > MaxCharsForTruncatedText)
    {
        Text = _fullText.Substring(0, MaxCharsForTruncatedText - 3) + "...";
    }

    InvalidateMeasure();
    ForceLayout(); // 强制触发布局更新
}

平板/iPad设备适配建议

  1. 动态截断规则:放弃固定字符数MaxCharsForTruncatedText,通过CalculateLineCount方法判断文本行数,超过2-3行时再进行截断,适配不同屏幕宽度。
  2. 字体适配:确保文本尺寸计算使用控件的实际字体参数,避免因平板字体放大导致行数估算错误。
  3. 响应式布局:在平板布局中可适当增大控件宽度或最大显示行数,提升文本可读性。

内容的提问来源于stack exchange,提问作者Divyesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 01:10:56