在 Xamarin.iOS 中的代码中创建 iOS 用户界面

iOS 应用的用户界面就像店面 - 应用程序通常获取一个窗口,但它可以根据需要使用任意数量的对象填充窗口,并且可以根据应用想要显示的内容更改对象和排列方式。 此情形中的对象(用户看到的内容)称为视图。 为了在应用程序中构建单个屏幕,视图会在内容视图层次结构中互相堆叠,该层次结构由单个视图控制器进行管理。 具有多个屏幕的应用程序具有多个内容视图层次结构(各自具有自己的视图控制器),应用程序会将视图置于窗口中以基于用户所处的屏幕创建不同的内容视图层次结构。

下图显示了窗口、视图、子视图与视图控制器之间的关系,它们向设备屏幕提供了用户界面:

This diagram illustrates the relationships between the Window, Views, Subviews, and View Controller

可以使用 Xcode 的 Interface Builder 构造这些视图层次结构,但最好对如何在代码中完全工作有基本的了解。 本文逐步讲解一些基本要点,以启动并运行仅代码的用户界面开发。

创建仅代码项目

iOS 空白项目模板

首先,使用文件新建项目 > Visual C# > i电话 和 iPad > iOS 应用(Xamarin)项目在 Visual Studio 中创建 iOS > 项目,如下所示:

New Project Dialog

然后选择 空白应用 项目模板:

Select a Template Dialog

空项目模板向项目添加 4 个文件:

Project Files

  1. AppDelegate.cs - 包含一个 UIApplicationDelegate 子类, AppDelegate 该类用于处理 iOS 中的应用程序事件。 应用程序窗口是在方法中创建 AppDelegateFinishedLaunching
  2. Main.cs - 包含应用程序的入口点,该应用程序指定类 AppDelegate
  3. Info.plist - 包含应用程序配置信息的属性列表文件。
  4. Entitlements.plist – 包含有关应用程序功能和权限的信息的属性列表文件。

iOS 应用程序是使用 MVC 模式生成的。 应用程序显示的第一个屏幕是从窗口的根视图控制器创建的。 有关 MVC 模式本身的更多详细信息,请参阅 Hello, iOS 多屏幕指南。

模板添加的 AppDelegate 实现将创建应用程序窗口,其中每个 iOS 应用程序只有一个,并使用以下代码使其可见:

public class AppDelegate : UIApplicationDelegate
{
    public override UIWindow Window
            {
                get;
                set;
            }

    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        // create a new window instance based on the screen size
        Window = new UIWindow(UIScreen.MainScreen.Bounds);

        // make the window visible
        Window.MakeKeyAndVisible();

        return true;
    }
}

如果现在要运行此应用程序,可能会引发一个异常,指出这一点 Application windows are expected to have a root view controller at the end of application launch。 让我们添加一个控制器,并将其设为应用的根视图控制器。

添加控制器

你的应用可以包含许多视图控制器,但它需要有一个根视图控制器来控制所有视图控制器。 通过创建 UIViewController 实例并将其设置为 Window.RootViewController 属性,将控制器添加到窗口中:

public class AppDelegate : UIApplicationDelegate
{
    // class-level declarations

    public override UIWindow Window
    {
        get;
        set;
    }

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        // create a new window instance based on the screen size
        Window = new UIWindow(UIScreen.MainScreen.Bounds);

        var controller = new UIViewController();
        controller.View.BackgroundColor = UIColor.LightGray;

        Window.RootViewController = controller;

        // make the window visible
        Window.MakeKeyAndVisible();

        return true;
    }
}

每个控制器都有一个可从属性访问的 View 关联视图。 上述代码将视图 BackgroundColor 的属性更改为 UIColor.LightGray 使其可见,如下所示:

The View's background is a visible light gray

也可以以这种方式设置任何 UIViewController 子类,包括 UIKit 中的控制器以及我们自己编写的子类 RootViewController 。 例如,以下代码将添加为UINavigationControllerRootViewController

public class AppDelegate : UIApplicationDelegate
{
    // class-level declarations

    public override UIWindow Window
    {
        get;
        set;
    }

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        // create a new window instance based on the screen size
        Window = new UIWindow(UIScreen.MainScreen.Bounds);

        var controller = new UIViewController();
        controller.View.BackgroundColor = UIColor.LightGray;
        controller.Title = "My Controller";

        var navController = new UINavigationController(controller);

        Window.RootViewController = navController;

        // make the window visible
        Window.MakeKeyAndVisible();

        return true;
    }
}

这会生成嵌套在导航控制器中的控制器,如下所示:

The controller nested within the navigation controller

创建视图控制器

现在,我们已经了解如何将控制器添加为 RootViewController 窗口,接下来让我们了解如何在代码中创建自定义视图控制器。

添加一个名为“ CustomViewController 如下所示”的新类:

类应继承自 UIViewController命名空间中的 UIKit 类,如下所示:

using System;
using UIKit;

namespace CodeOnlyDemo
{
    class CustomViewController : UIViewController
    {
    }
}

初始化视图

UIViewController 包含一个调用 ViewDidLoad 的方法,该方法在视图控制器首次加载到内存中时调用。 这是一个适当的位置,用于初始化视图,例如设置视图的属性。

例如,以下代码添加一个按钮和一个事件处理程序,以便在按下按钮时将新的视图控制器推送到导航堆栈:

using System;
using CoreGraphics;
using UIKit;

namespace CodyOnlyDemo
{
    public class CustomViewController : UIViewController
    {
        public CustomViewController ()
        {
        }

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

            View.BackgroundColor = UIColor.White;
            Title = "My Custom View Controller";

            var btn = UIButton.FromType (UIButtonType.System);
            btn.Frame = new CGRect (20, 200, 280, 44);
            btn.SetTitle ("Click Me", UIControlState.Normal);

            var user = new UIViewController ();
            user.View.BackgroundColor = UIColor.Magenta;

            btn.TouchUpInside += (sender, e) => {
                this.NavigationController.PushViewController (user, true);
            };

            View.AddSubview (btn);

        }
    }
}

若要在应用程序中加载此控制器并演示简单的导航,请创建一个新实例 CustomViewController。 创建新的导航控制器,传入视图控制器实例,并将新的导航控制器设置为窗口的AppDelegate前面RootViewController所示:

var cvc = new CustomViewController ();

var navController = new UINavigationController (cvc);

Window.RootViewController = navController;

现在,当应用程序加载时,将 CustomViewController 加载到导航控制器中:

The CustomViewController is loaded inside a navigation controller

单击该按钮,将 新的视图控制器推送 到导航堆栈:

A new View Controller pushed onto the navigation stack

生成视图层次结构

在上面的示例中,我们通过向视图控制器添加按钮,开始在代码中创建用户界面。

iOS 用户界面由视图层次结构组成。 其他视图(如标签、按钮、滑块等)作为某些父视图的子视图添加。

例如,让我们编辑 CustomViewController 以创建登录屏幕,用户可以在其中输入用户名和密码。 屏幕将包含两个文本字段和一个按钮。

添加文本字段

首先,删除在“初始化视图”部分中添加的按钮和事件处理程序。

通过创建和初始化 UITextField 用户名,然后将其添加到视图层次结构来添加用户名的控件,如下所示:

class CustomViewController : UIViewController
{
    UITextField usernameField;

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

        View.BackgroundColor = UIColor.Gray;

        nfloat h = 31.0f;
        nfloat w = View.Bounds.Width;

        usernameField = new UITextField
        {
            Placeholder = "Enter your username",
            BorderStyle = UITextBorderStyle.RoundedRect,
            Frame = new CGRect(10, 82, w - 20, h)
        };

        View.AddSubview(usernameField);
    }
}

创建 UITextField时,我们将属性 Frame 设置为定义其位置和大小。 在 iOS 中,0,0 坐标位于左上角,右侧为 +x,向下 +y。 设置 Frame 与其他几个属性一起后,我们调用 View.AddSubview 将其添加到 UITextField 视图层次结构。 usernameField这使得属性引用的UIView实例View的子视图。 子视图的添加顺序高于其父视图的 z 顺序,因此它将显示在屏幕上的父视图前面。

包含 UITextField 的应用程序如下所示:

The application with the UITextField included

我们可以以类似的方式添加 UITextField 密码,但这次我们才将 SecureTextEntry 属性设置为 true,如下所示:

public class CustomViewController : UIViewController
{
    UITextField usernameField, passwordField;
    public override void ViewDidLoad()
    {
       // keep the code the username UITextField
        passwordField = new UITextField
        {
            Placeholder = "Enter your password",
            BorderStyle = UITextBorderStyle.RoundedRect,
            Frame = new CGRect(10, 114, w - 20, h),
            SecureTextEntry = true
        };

      View.AddSubview(usernameField);
      View.AddSubview(passwordField);
   }
}

设置 SecureTextEntry = true 可隐藏用户输入 UITextField 的文本,如下所示:

Setting SecureTextEntry true hides the text entered by the user

添加按钮

接下来,我们将添加一个按钮,以便用户可以提交用户名和密码。 该按钮将像任何其他控件一样添加到视图层次结构中,方法是再次将其作为参数传递给父视图 AddSubview 的方法。

以下代码添加该按钮并注册事件的 TouchUpInside 事件处理程序:

var submitButton = UIButton.FromType (UIButtonType.RoundedRect);

submitButton.Frame = new CGRect (10, 170, w - 20, 44);
submitButton.SetTitle ("Submit", UIControlState.Normal);

submitButton.TouchUpInside += (sender, e) => {
    Console.WriteLine ("Submit button pressed");
};

View.AddSubview(submitButton);

就地显示登录屏幕,如下所示:

The login screen

与以前版本的 iOS 不同,默认按钮背景是透明的。 更改按钮 BackgroundColor 的属性将更改:

submitButton.BackgroundColor = UIColor.White;

这将导致一个正方形按钮,而不是典型的圆角边缘按钮。 若要获取舍入边缘,请使用以下代码片段:

submitButton.Layer.CornerRadius = 5f;

通过这些更改,视图将如下所示:

An example run of the view

将多个视图添加到视图层次结构

iOS 提供了一个工具,用于使用 AddSubviews...将多个视图添加到视图层次结构。

View.AddSubviews(new UIView[] { usernameField, passwordField, submitButton });

添加按钮功能

单击按钮时,用户将期望会发生某种情况。 例如,显示警报或导航到另一个屏幕。

让我们添加一些代码,将第二个视图控制器推送到导航堆栈。

首先,创建第二个视图控制器:

var loginVC = new UIViewController () { Title = "Login Success!"};
loginVC.View.BackgroundColor = UIColor.Purple;

然后,将功能添加到 TouchUpInside 事件:

submitButton.TouchUpInside += (sender, e) => {
                this.NavigationController.PushViewController (loginVC, true);
            };

导航如下所示:

The navigation is illustrated in this chart

请注意,默认情况下,使用导航控制器时,iOS 会向应用程序提供一个导航栏和一个后退按钮,以允许你移动回堆栈。

循环访问视图层次结构

可以循环访问子视图层次结构并选取任何特定视图。 例如,若要查找每个 UIButton 按钮并提供不同的 BackgroundColor按钮,可以使用以下代码片段

foreach(var subview in View.Subviews)
{
    if (subview is UIButton)
    {
         var btn = subview as UIButton;
         btn.BackgroundColor = UIColor.Green;
    }
}

但是,如果要迭代的视图是一个UIView,因为所有视图都将作为添加到父视图的对象本身继承UIView时返回UIView,则此方法不起作用。

处理旋转

如果用户将设备旋转到横向,控件不会适当调整大小,如以下屏幕截图所示:

If the user rotates the device to landscape, the controls do not resize appropriately

解决此问题的一种方法是在每个视图上设置 AutoresizingMask 属性。 在这种情况下,我们希望控件水平拉伸,因此我们将设置每个 AutoresizingMask控件。 以下示例适用于 usernameField,但同样需要应用于视图层次结构中的每个小工具。

usernameField.AutoresizingMask = UIViewAutoresizing.FlexibleWidth;

现在,当我们旋转设备或模拟器时,一切会拉伸以填充额外的空间,如下所示:

All the controls stretch to fill the additional space

创建自定义视图

除了使用属于 UIKit 的控件外,还可以使用自定义视图。 可以通过继承 UIView 和重写 Draw来创建自定义视图。 让我们创建自定义视图并将其添加到视图层次结构以演示。

从 UIView 继承

我们需要做的第一件事是为自定义视图创建类。 我们将使用 Visual Studio 中的类 模板执行此操作,以添加名为 CircleView 的空类。 应将基类设置为 UIView命名空间中的 UIKit 基类。 我们还需要命名空间 System.Drawing 。 此示例中不会使用其他各种 System.* 命名空间,因此可以随意删除它们。

此类应如下所示:

using System;

namespace CodeOnlyDemo
{
    class CircleView : UIView
    {
    }
}

在 UIView 中绘图

每个 UIView 方法都有一个 Draw 在需要绘制时由系统调用的方法。 Draw 不应直接调用。 它在运行循环处理过程中由系统调用。 第一次在视图层次结构中添加视图后通过运行循环,将调用其 Draw 方法。 当视图被标记为需要通过调用视图SetNeedsDisplaySetNeedsDisplayInRect对视图进行绘制时,将发生后续调用Draw

通过在重写 Draw 的方法中添加此类代码,我们可以将绘图代码添加到视图中,如下所示:

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

    //get graphics context
    using (var g = UIGraphics.GetCurrentContext())
    {
        // set up drawing attributes
        g.SetLineWidth(10.0f);
        UIColor.Green.SetFill();
        UIColor.Blue.SetStroke();

        // create geometry
        var path = new CGPath();
        path.AddArc(Bounds.GetMidX(), Bounds.GetMidY(), 50f, 0, 2.0f * (float)Math.PI, true);

        // add geometry to graphics context and draw
        g.AddPath(path);
        g.DrawPath(CGPathDrawingMode.FillStroke);
    }
}

由于 CircleView 是一个 UIView,我们也可以设置 UIView 属性。 例如,我们可以在 BackgroundColor 构造函数中设置:

public CircleView()
{
    BackgroundColor = UIColor.White;
}

若要使用CircleView刚刚创建的视图,我们可以将其作为子视图添加到现有控制器中的视图层次结构,就像之前一UILabelsUIButton样,也可以将其加载为新控制器的视图。 让我们做后者。

加载视图

UIViewController 具有由控制器调用以创建其视图的方法 LoadView 。 这是创建视图并将其分配给控制器属性的适当 View 位置。

首先,我们需要一个控制器,因此请创建一个名为 CircleController 的新空类。

CircleController 添加以下代码来设置 View CircleView />(不应在重写中调用 base 实现):

using UIKit;

namespace CodeOnlyDemo
{
    class CircleController : UIViewController
    {
        CircleView view;

        public override void LoadView()
        {
            view = new CircleView();
            View = view;
        }
    }
}

最后,我们需要在运行时呈现控制器。 为此,请在之前添加的提交按钮上添加事件处理程序,如下所示:

submitButton.TouchUpInside += delegate
{
    Console.WriteLine("Submit button clicked");

    //circleController is declared as class variable
    circleController = new CircleController();
    PresentViewController(circleController, true, null);
};

现在,当我们运行应用程序并点击“提交”按钮时,将显示带圆圈的新视图:

The new view with a circle is displayed

创建启动屏幕

当应用启动时,会显示启动屏幕,以向用户显示响应式屏幕。 由于应用加载时会显示启动屏幕,因此无法在代码中创建它,因为应用程序仍在内存中加载。

在 Visual Studio 中创建 iOS 项目时,会以 .xib 文件的形式提供启动屏幕,可在项目内的 Resources 文件夹中找到该文件。

可以通过双击它并在 Xcode 接口生成器中打开它来编辑它。

Apple 建议将 .xib 或 Storyboard 文件用于面向 iOS 8 或更高版本的应用程序,当你在 Xcode Interface Builder 中启动任一文件时,可以使用大小类和自动布局来调整布局,使其看起来良好,并且对所有设备大小正确显示。 除了 .xib 或 Storyboard 之外,还可以使用静态启动映像来支持面向早期版本的应用程序。

有关创建启动屏幕的详细信息,请参阅以下文档:

重要

从 iOS 9 开始,Apple 建议将情节提要用作创建启动屏幕的主要方法。

为 iOS 8 应用程序创建启动映像

如果应用程序面向 iOS 8 以前的版本,除了 .xib 或 Storyboard 启动屏幕之外,还可以使用静态图像。

可以在 Info.plist 文件中设置此静态图像,也可以设置为应用程序中的资产目录(for iOS 7)。 你需要为每个设备大小(320x480、640x960、640x1136)提供单独的图像,应用程序可以运行。 有关启动屏幕大小的详细信息,请查看 “启动屏幕图像 ”指南。

重要

如果你的应用没有“启动屏幕”,你可能会注意到它无法完全适应屏幕。 如果是这种情况,应确保至少包含一个 640x1136 映像,该图像命名 Default-568@2x.png 为 Info.plist。

总结

本文讨论了如何在 Visual Studio 中以编程方式开发 iOS 应用程序。 我们介绍了如何从空项目模板生成项目,讨论如何创建根视图控制器并将其添加到窗口中。 然后,我们演示了如何使用 UIKit 中的控件在控制器中创建视图层次结构,以开发应用程序屏幕。 接下来,我们研究了如何使视图以不同的方向适当布局,并了解了如何通过子类 UIView化创建自定义视图,以及如何在控制器中加载视图。 最后,我们探索了如何将启动屏幕添加到应用程序。