结合使用自定义控件和 iOS 设计器

警告

iOS Designer 在 Visual Studio 2019 版本 16.8 和 Visual Studio 2019 for Mac 版本 8.8 中已经弃用,并且已从 Visual Studio 2019 版本 16.9 和 Visual Studio for Mac 版本 8.9 中移除。 要生成 iOS 用户界面,建议直接在运行 Xcode 的 Mac 上操作。 有关详细信息,请参阅用 Xcode 设计用户界面

要求

Xamarin Designer for iOS 在 Visual Studio for Mac 和 Windows 上的 Visual Studio 2017 及更高版本中可用。

此指南假设读者熟悉入门指南中介绍的内容。

演练

重要

从 Xamarin.Studio 5.5 开始,创建自定义控件的方式与早期版本略有不同。 若要创建自定义控件,IComponent 接口是必需的(以及关联的实现方法),或者可使用 [DesignTimeVisible(true)] 对该类进行批注。 在下面的演练示例中使用了后一种方法。

  1. 从“iOS”>“应用”>“单一视图应用程序”>“C#”模板,创建一个新解决方案,将其命名为 ScratchTicket,然后继续完成“新建项目”向导

    Create a new solution

  2. 创建一个名为 ScratchTicketView 的新空类文件:

    Create a new ScratchTicketView class

  3. ScratchTicketView 类添加以下代码:

    using System;
    using System.ComponentModel;
    using CoreGraphics;
    using Foundation;
    using UIKit;
    
    namespace ScratchTicket
    {
        [Register("ScratchTicketView"), DesignTimeVisible(true)]
        public class ScratchTicketView : UIView
        {
            CGPath path;
            CGPoint initialPoint;
            CGPoint latestPoint;
            bool startNewPath = false;
            UIImage image;
    
            [Export("Image"), Browsable(true)]
            public UIImage Image
            {
                get { return image; }
                set
                {
                    image = value;
                    SetNeedsDisplay();
                }
            }
    
            public ScratchTicketView(IntPtr p)
                : base(p)
            {
                Initialize();
            }
    
            public ScratchTicketView()
            {
                Initialize();
            }
    
            void Initialize()
            {
                initialPoint = CGPoint.Empty;
                latestPoint = CGPoint.Empty;
                BackgroundColor = UIColor.Clear;
                Opaque = false;
                path = new CGPath();
                SetNeedsDisplay();
            }
    
            public override void TouchesBegan(NSSet touches, UIEvent evt)
            {
                base.TouchesBegan(touches, evt);
    
                var touch = touches.AnyObject as UITouch;
    
                if (touch != null)
                {
                    initialPoint = touch.LocationInView(this);
                }
            }
    
            public override void TouchesMoved(NSSet touches, UIEvent evt)
            {
                base.TouchesMoved(touches, evt);
    
                var touch = touches.AnyObject as UITouch;
    
                if (touch != null)
                {
                    latestPoint = touch.LocationInView(this);
                    SetNeedsDisplay();
                }
            }
    
            public override void TouchesEnded(NSSet touches, UIEvent evt)
            {
                base.TouchesEnded(touches, evt);
                startNewPath = true;
            }
    
            public override void Draw(CGRect rect)
            {
                base.Draw(rect);
    
                using (var g = UIGraphics.GetCurrentContext())
                {
                    if (image != null)
                        g.SetFillColor((UIColor.FromPatternImage(image).CGColor));
                    else
                        g.SetFillColor(UIColor.LightGray.CGColor);
                    g.FillRect(rect);
    
                    if (!initialPoint.IsEmpty)
                    {
                        g.SetLineWidth(20);
                        g.SetBlendMode(CGBlendMode.Clear);
                        UIColor.Clear.SetColor();
    
                        if (path.IsEmpty || startNewPath)
                        {
                            path.AddLines(new CGPoint[] { initialPoint, latestPoint });
                            startNewPath = false;
                        }
                        else
                        {
                            path.AddLineToPoint(latestPoint);
                        }
    
                        g.SetLineCap(CGLineCap.Round);
                        g.AddPath(path);
                        g.DrawPath(CGPathDrawingMode.Stroke);
                    }
                }
            }
        }
    }
    
  4. FillTexture.pngFillTexture2.pngMonkey.png 文件(GitHub 中提供)添加到“资源”文件夹中

  5. 双击 Main.storyboard 文件以在设计器中将其打开:

    The iOS Designer

  6. 将一个图像视图从“工具箱”拖放到情节提要中的视图中

    An Image View added to the layout

  7. 选择该图像视图,并将其“图像”属性更改为 Monkey.png

    Setting Image View Image property to Monkey.png

  8. 由于使用的是大小类,因此需要限制此图像视图。 单击该图像两次,将其置于限制模式。 通过单击中心固定图柄将其限制到中心,并将其垂直和水平对齐:

    Centering the image

  9. 若要限制高度和宽度,请单击大小固定图柄(“骨”形状的图柄)并分别选择宽度和高度:

    Adding Constraints

  10. 通过单击工具栏中的更新按钮,根据限制更新框架:

    The Constraints toolbar

  11. 接下来,生成项目,使“刮刮卡视图”显示在工具箱中的“自定义组件”下

    The Custom Components toolbox

  12. 拖放一个刮刮卡视图,使其显示在猴子图像上。 调整拖动图柄,使刮刮卡视图完全覆盖猴子,如下所示:

    A Scratch Ticket View over the Image View

  13. 通过绘制范围框以选择这两个视图,将刮刮卡视图限制为图像视图。 选择这些选项以将其限制到相应宽度、高度、中心和中间,并根据限制更新框架,如下所示:

    Centering and adding Constraints

  14. 运行应用程序并“刮掉”图像以显示猴子。

    A sample app run

添加设计时属性

设计器还包括对 numeric、enumeration、string、bool、CGSize、UIColor 和 UIImage 属性类型的自定义控件的设计时支持。 为了演示,将一个属性添加到 ScratchTicketView 以设置“刮掉”的图像。

将以下代码添加到属性的 ScratchTicketView 类:

[Export("Image"), Browsable(true)]
public UIImage Image
{
    get { return image; }
    set {
            image = value;
              SetNeedsDisplay ();
        }
}

可能还需要向 Draw 方法添加 null 检查,如下所示:

public override void Draw(CGRect rect)
{
    base.Draw(rect);

    using (var g = UIGraphics.GetCurrentContext())
    {
        if (image != null)
            g.SetFillColor ((UIColor.FromPatternImage (image).CGColor));
        else
            g.SetFillColor (UIColor.LightGray.CGColor);

        g.FillRect(rect);

        if (!initialPoint.IsEmpty)
        {
             g.SetLineWidth(20);
             g.SetBlendMode(CGBlendMode.Clear);
             UIColor.Clear.SetColor();

             if (path.IsEmpty || startNewPath)
             {
                 path.AddLines(new CGPoint[] { initialPoint, latestPoint });
                 startNewPath = false;
             }
             else
             {
                 path.AddLineToPoint(latestPoint);
             }

             g.SetLineCap(CGLineCap.Round);
             g.AddPath(path);
             g.DrawPath(CGPathDrawingMode.Stroke);
        }
    }
}

包含一个 ExportAttributeBrowsableAttribute 且参数设置为 true 会导致该属性显示在设计器的“属性”面板中。 将属性更改为项目附带的另一个图像(如 FillTexture2.png)会导致该控件在设计时更新,如下所示:

Editing Design Time properties

总结

在本文中,我们演练了如何创建自定义控件,以及如何使用 iOS 设计器在 iOS 应用程序中使用该控件。 我们了解了如何创建和生成该控件,使其可用于设计器的工具箱中的应用程序。 此外,我们还研究了如何实现该控件,使其在设计时和运行时都能正确呈现,以及如何在设计器中公开自定义控件属性。