何时在 Xamarin Forms 中初始化 UITextView 的边界和字体?

When are the bounds and font of a UITextView initialized in Xamarin Forms?

我正在尝试为 Xamarin.Forms 项目实现具有提示文本功能的编辑器。这在 Android 中是微不足道的,因为底层的 EntryEditText 控件有一个 Hint 属性。在 iOS 中,实现有点复杂,因为 UITextView class 没有实现提示文本。

我不喜欢这种技巧,"set text to the placeholder, clear it if typing starts, return it if typing ends and the text is blank"。这意味着我必须做额外的工作来判断控件是否为空白,并且涉及的文本颜色有很多摆弄。但我遇到了很多麻烦,我将不得不求助于它。也许有人可以帮我解决这个问题。

我从 Placeholder in UITextView 的答案开始。我开始了一个新的 Xamarin iOS 项目,偶然发现了一个粗略的 Obj-C 到 C# 的转换,它工作得很好,但有一个小的变化:UITextView 的 Font 属性尚未在构造函数中初始化,所以我有覆盖 AwakeFromNib() 以设置占位符标签的字体。我对其进行了测试并且它有效,所以我将该文件放入 Xamarin Forms 项目中,然后事情开始变得有点疯狂。

第一个问题是 MonoTouch 显然在 Xamarin Forms 中有一些细微的 API 差异,例如使用 RectangleF 等类型而不是 CGRect。如果不是出乎意料的话,这是显而易见的。在过去的几天里,我一直在与其他一些差异作斗争,但似乎无法以让我开心的方式克服它们。这是我的文件,由于我一直在尝试各种调试方式,因此被大幅缩减:

using System;
using MonoTouch.UIKit;
using MonoTouch.Foundation;
using MonoTouch.CoreGraphics;
using System.Drawing;

namespace TestCustomRenderer.iOS {
    public class PlaceholderTextView : UITextView {

        private UILabel _placeholderLabel;
        private NSObject _notificationToken;

        private const double UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION = 0.25;

        private string _placeholder;
        public string Placeholder {
            get {
                return _placeholder;
            }
            set {
                _placeholder = value;
                if (_placeholderLabel != null) {
                    _placeholderLabel.Text = _placeholder;
                }
            }
        }

        public PlaceholderTextView() : base(RectangleF.Empty) {
            Initialize();
        }

        private void Initialize() {
            _notificationToken = NSNotificationCenter.DefaultCenter.AddObserver(TextDidChangeNotification, HandleTextChanged);
            _placeholderLabel = new UILabel(new RectangleF(8, 8, this.Bounds.Size.Width - 16, 25)) {
                LineBreakMode = UILineBreakMode.WordWrap,
                Lines = 1,
                BackgroundColor = UIColor.Green,
                TextColor = UIColor.Gray,
                Alpha = 1.0f,
                Text = Placeholder
            };

            AddSubview(_placeholderLabel);
            _placeholderLabel.SizeToFit();
            SendSubviewToBack(_placeholderLabel);
        }

        public override void DrawRect(RectangleF area, UIViewPrintFormatter formatter) {
            base.DrawRect(area, formatter);

            if (Text.Length == 0 && Placeholder.Length > 0) {
                _placeholderLabel.Alpha = 1;
            }
        }

        private void HandleTextChanged(NSNotification notification) {
            if (Placeholder.Length == 0) {
                return;
            }

            UIView.Animate(UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION, () => {
                if (Text.Length == 0) {
                    _placeholderLabel.Alpha = 1;
                } else {
                    _placeholderLabel.Alpha = 0;
                }
            });
        } 

        public override void AwakeFromNib() {
            base.AwakeFromNib();

            _placeholderLabel.Font = this.Font;
        }

        protected override void Dispose(bool disposing) {
            base.Dispose(disposing);

            if (disposing) {
                NSNotificationCenter.DefaultCenter.RemoveObserver(_notificationToken);
                _placeholderLabel.Dispose();
            }
        }

    }
}

此处的一个显着变化是将标签的初始化从 DrawRect() 重新定位到构造函数。据我所知,Xamarin 从不让 DrawRect() 被调用。您还会注意到我没有设置 Font 属性。结果在 iOS MonoTouch 项目中,有时父字体为 null 并且将标签的字体设置为 null 也是非法的。似乎在构建 Xamarin 后的某个时刻设置了字体,因此在 AwakeFromNib() 中设置该属性是安全的。

我写了一个快速的 Editor-derived class 和一个自定义呈现器,因此 Xamarin Forms 可以呈现控件,Renderer 有点值得注意,因为我是从 NativeRenderer 而不是 EditorRenderer 派生的。我需要从覆盖的 OnModelSet() 调用 SetNativeControl(),但查看程序集查看器显示 EditorRenderer 进行了一些私人调用,我必须在我的中重新实现。嘘。没有发布,因为这已经很大了,但如果需要我可以编辑它。

上面的代码值得注意,因为占位符根本不可见。它看起来像在面向 iOS 的 MonoTouch 中,您通常使用框架初始化控件,并且调整大小是一种非常罕见的情况,您可以假设它不会发生。在 Xamarin Forms 中,布局由布局容器执行,因此构造函数提供的框架无关紧要。但是,标签的大小是要在构造函数中设置的,因此它最终具有负宽度。哎呀

我认为这可以通过将标签的实例化移动到 AwakeFromNib() 中来解决,或者至少在那里调整它的大小。这是我发现由于某种原因,控件中未调用 AwakeFromNib() 的时候。好吧。我试图找到一个等价的 callback/event ,它发生得足够晚以设置边界,但在 iOS 端找不到任何东西。在尝试了很多很多事情之后,我注意到自定义渲染器收到了这个混乱的 Xamarin Forms 模型端的属性更改事件。因此,如果我侦听 Height/Width 更改事件,我就可以调用标签上的方法以根据当前控件为其提供合理的大小。这就暴露了另一个问题。

我找不到设置标签字体以匹配 UITextView 字体的方法。在构造函数中,Font 属性为空。在 iOS 和 Xamarin Forms 项目中都是如此。在 iOS 项目中,调用 AwakeFromNib() 时,属性已初始化,一切正常。在 XF 项目中,它从未被调用过,甚至当我从一个 5 秒延迟的任务中调用一个方法(以确保显示控件)这样的特技时,该属性仍然为空。

Logic 和 iOS 文档规定字体的默认值应为 17 磅 Helvetica。如果我捏造大小以使其可见,则占位符标签也是如此。对于 UITextView 控件而言,情况并非如此,但由于它将其字体报告为 null,所以我看不到字体实际是什么。当然,如果我手动设置一切都很好,但我希望能够处理默认情况。这似乎是一个错误;该框似乎在其字体周围。我感觉这与 Xamarin.Forms.Editor class 没有 Font 属性的任何原因有关。

所以我正在寻找两个问题的答案:

  1. 如果我在 XF 中扩展 iOS 控件以添加子视图,处理该子视图大小的最佳方法是什么?我发现 Height/Width 更改会在渲染器中引发事件,这是唯一可用的方法吗?
  2. 当用户未设置该属性时,Xamarin Forms 中的 UITextView 的字体是否曾经设置为非空值?我 可以 接受此控件需要显式设置字体的要求,但它令人讨厌,我想避免它。

我希望我错过了一些明显的东西,因为我开始找错树了。

If I'm extending an iOS control in XF to add a subview, what is the best way to handle sizing that subview? I've found Height/Width changes raise events in the renderer, is this the only available way?

这是我所知道的唯一方法,因为渲染器的暴露元素非常有限。

When the property has not been set by a user, is the Font of a UITextView in Xamarin Forms ever set to a non-null value? I can live with a requirement that this control requires the font to be explicitly set, but it's yucky and I'd like to avoid it.

不,没有为字体分配默认的非空值。