iOS Designer でのカスタム コントロールの使用

警告

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. サイズ クラスを使用しているため、このイメージ ビューを制約する必要があります。 イメージを 2 回クリックして、制約モードにします。 中心固定ハンドルをクリックして中心に制約し、垂直方向と水平方向の両方に揃えます。

    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

デザイン時のプロパティの追加

デザイナーには、プロパティ型の数値、列挙型、文字列、ブール値、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 Designer を使用する iOS アプリケーションで使用する方法について説明しました。 デザイナーの [ツールボックス] にあるアプリケーションで使用できるようにするために、コントロールを作成してビルドする方法を確認しました。 さらに、デザイン時と実行時の両方で適切にレンダリングされるようにコントロールを実装する方法と、デザイナーでカスタム コントロール プロパティを公開する方法についても確認しました。